js
数据类型
基本的数据类型介绍
在 JS 中共有 8 种基础的数据类型,分别为: Undefined 、 Null 、 Boolean 、 Number 、 String 、 Object 、 Symbol 、 BigInt 。 其中 Symbol 和 BigInt 是 ES6 新增的数据类型,可能会被单独问:
Symbol 代表独一无二的值,最大的用法是用来定义对象的唯一属性名。 BigInt 可以表示任意大小的整数。
值类型和引用类型的理解
值类型是直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储; 引用类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;
事件
事件冒泡 √✨
当一个事件在 DOM 元素上触发时,会冒泡到父元素,直到祖先元素
事件委托/代理 √✨
事件委托是将事件监听器添加到父元素,而不是每个子元素单独设置事件监听器,当触发子元素时,事件会冒泡到父元素,监听器就会触发。
事件委托的好处是:
- 内存占用减少,因为只需要一个父元素的事件,而不必为每个后代都添加事件
- 无需从已删除的元素中解绑事件,也无需将事件绑定到新元素上
事件循环机制 √
背景 js 是单线程语言,通过事件循环机制,来协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程阻塞。
一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为 macro-task(宏任务)与 micro-task(微任务)。
事件循环就是执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。
宏任务:
- script(整体代码)
- setTimeout
- setInterval
- setImmediate
- I/O
- UI render
微任务:
- process.nextTick
- Promise
- Async/Await(实际就是 promise)
- MutationObserver(html5 新特性)
for 循环中变量的值
给出以下代码,输出的结果是什么?原因?
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
console.log(i);
参考回答: 立即输出 5 个 5,并且在一秒后输出 5 个 5 每次 for 循环的时候 setTimeout 都会执行,但是里面的 function 则不会执行被放入任务队 列,因此放了 5 次;for 循环的 5 次执行完之后不到 1000 毫秒;1000 毫秒后全部执行任 务队列中的函数,所以就是输出 5 个 5。
作用域
怎么理解 this √✨
this 是当前执行上下文的一个属性,函数的调用方式决定了 this 的值,this 取值符合以下 6 个规则:
- 在调用函数时使用 new 关键字,this 是一个全新的对象
const obj = {
name: "objName",
fn1: function () {
console.log(this);
},
};
new obj.fn1();
// fn1 {}
- 通过 apply/call/bind 调用或者创建函数,this 就是 apply/call/bind 的第一个参数
global.name = "globalName";
const obj = {
name: "objName",
fn1: function () {
console.log(this);
},
};
obj.fn1.apply(global);
// global
- 通过对象.函数调用时,this 就是该对象
const obj = {
name: "objName",
fn1: function () {
console.log(this);
},
};
obj.fn1();
// obj
- 如果调用函数不符合上述规则,this 指向全局对象
- 如果符合多个规则,则较高的规则,1 最高,4 最低,决定 this 的指向
- 如果函数是箭头函数,this 是它被创建时的上下文
var name = "windowsName";
var a = {
name: "Cherry",
func1: function () {
console.log(this.name);
},
func2: function () {
setTimeout(() => {
console.log(this);
}, 100);
},
};
a.func2(); // Cherry
call/apply/bind 的区别 √✨
call/apply 用于调用函数,第一个参数是 this,apply 第二个参数是数组,call 则是逗号分隔,可传任意长度的参数
bind 用于创建函数,第一个参数是 this,后面的参数是逗号分隔,可传任意长度的参数
对闭包的理解 √✨
https://juejin.cn/post/6937469222251560990 闭包就是有权访问另一个函数作用域变量的函数。
作用:
- 保护函数的私有变量不受外部干扰,形成不销毁的栈内存
- 把函数内的值保存下来,实现方法和属性的私有化
常见的 7 个使用场景:
- 函数 return 一个函数
- 函数作为参数
- IIFE,立即执行函数
- 循环赋值
- 使用回调函数就是使用闭包
- 节流防抖
- 柯里化实现
继承
JS 如何实现继承
继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联(原型对象指针),这样,一个对象就可以通过委托访问另一个对象的属性和函数
理解原型继承
https://juejin.cn/post/6934498361475072014
所有 js 对象都有一个proto属性,指向它的原型对象,当试图访问一个对象的属性时,如果没有在该对象上找到,就会搜寻该对象的原型,以及该对象的原型的原型,层层向上搜索,直到找到一个名字匹配的属性或者到达原型链的末尾。
const obj = { a: 1 };
obj.toString(); // 原型链上找到的toString方法
变量
变量提升 √✨
声明变量会提升到当前作用域的顶部
null、undefined、未声明变量之间的区别 √✨
未声明变量会脱离当前作用域,成为全局作用域下定义的变量,在严格模式下,给未声明的变量赋值,会抛出错误
undefined 是声明变量但未赋值。
null 是声明变量赋值 null,表示空值
== 和 === 的区别是什么? √✨
==是在类型转换后再比较,===是严格相等进行比较
Set、Map、WeakSet 和 WeakMap 的区别? √✨
https://es6.ruanyifeng.com/#docs/set-map
set:类似于数组,成员的值都是唯一的,没有重复的值
weakSet:不重复的值的集合,成员只能是对象,弱引用,如果不被其他对象引用,会被垃圾回收机制自动回收该对象占用的内存。
map:类似于对象,键可以是各种类型的值,包括对象
weakMap:生成键值对的集合,键只能是对象,null 除外,键名所引用的对象是弱引用,如果不被其他对象引用,会被垃圾回收机制自动回收该对象占用的内存。
ajax
ajax
ajax 是创建异步 web 应用的一种技术。
通过 ajax,web 应用可以异步向服务器发送数据
jsonp 原理 √✨
通过 script 标签发送跨域请求,通过 callback 查询参数
同源策略
同源策略可防止 JS 发起跨域请求,源被定义为 URI、主机名、端口号的组合。
同源策略可以防止页面上的恶意脚本访问另一个网页的敏感数据。
模块化
CommonJS、AMD、CMD、ES6 的了解
https://juejin.cn/post/6844903541853650951
CommonJS:
- 定义模块:一个文件就是一个模块,每一个模块都是一个单独的作用域
- 模块输出:模块只有一个出口,把需要导出的内容放到 module.exports 对象
- 加载模块:用 require 方法,读取一个文件并执行,返回文件内部的 module.exports 对象
- 用同步的方式加载模块
- 在服务端,模块文件都存在本地磁盘,读取非常快。
- 但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
- 而且浏览器端加载 js 的方式是 script 标签,脚本标签天生异步,传统 CommonJS 模块在浏览器环境中无法正常加载
AMD:异步模块定义,在浏览器端模块化开发的规范
- RequireJS 解决问题
- 多个 js 文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
- js 加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长
- 语法
- define 函数定义模块,require 函数加载模块
- 原理:require 函数在加载依赖的函数的时候是异步加载的,这样浏览器不会失去响应,它指定的回调函数,只有前面的模块都加载成功后,才会运行。解决了依赖性问题。
- 推崇依赖前置,在定义模块的时候就要声明其依赖的模块,用户体验好,因为没有延迟,依赖模块提前执行了
- RequireJS 解决问题
CMD:通用模块定义
- SeaJS
- define 函数参数和 RequireJS 不同
- 推崇就近依赖,只有在用到某个模块的时候再去 require,性能好,因为只有用户需要的时候才执行
ES6 module
- export 输出模块
- import 加载模块
UMD
- 是 AMD 和 CommonJS 的糅合,跨平台的解决方案
- UMD 先判断是否支持 Node.js,存在则使用 Node.js 模块模式。再判断是否支持 AMD,存在则使用 AMD 方式加载模块
工具函数
防抖
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/5
防抖:触发高频事件后 n 秒内,函数只执行一次,如果 n 秒内高频事件再次触发,则重新计算时间
实现原理:每次触发事件时都取消之前的延时调用方法
function debounce(fn) {
let timer = null;
return () => {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, 500);
};
}
节流
节流:高频事件触发,但在 n 秒内,只会执行一次,节流会稀释函数的执行频率
实现原理:每次触发事件时都判断当前是否有等待执行的延时函数
function throttle(fn) {
let canRun = true;
return () => {
if (!canRun) return;
canRun = false;
setTimeout(() => {
fn.apply(this, arguments);
canRun = true;
}, 500);
};
}
深度优先遍历和广度优先遍历
深度优先遍历,沿着树的深度遍历树的节点,尽可能深的搜索树的分支,属于盲目搜索,无法保证搜索到的路径是最短路径
广度优先遍历,沿着图的宽度遍历节点,属于盲目搜索,无法保证搜索到的路径是最短路径
对 Promise 的理解
Promise 是一个在未来某个时间产生结果的对象,可能处于以下三种状态:fulfilled、rejected、pending。
优点:
- 避免回调地狱
- then 方法简单易读
- all/allsetted 并行执行异步代码
请解释下面代码为什么不能用作 IIFE:function foo(){}(),需要做出哪些修改才能成为 IIFE
IIFE 代表立即执行函数,JS 解析器会把 function foo(){}()解析成 function foo(){}和(),function foo(){}是函数声明,()调用函数却没有指定名称,会抛出 Uncaught SyntasxError:Unexpected token 错误
修改方式是再加一对括号,(function foo(){})()或者(function foo(){}()),这样些不会暴露到全局作用域,可以省略函数的名称。
判断浏览器型号
- 功能检测,'geolocation in navigator',不会在不支持的浏览器出现奔溃和错误
- UA 字符串,很难解析并且可能存在欺骗性,例如,Chrome 会同时作为 Chrome 和 Safari 进行报告,不建议使用
优化
单页应用是什么,怎么对 SEO 友好
单页应用指的是客户端渲染,浏览器从服务器加载初始页面,整个 web 应用所需的脚本和样式文件。当用户导航到其他页面时,不会触发页面刷新。
优点:
- 用户感知响应快
- http 请求减少,不必下载相同的资源
缺点:
- 首次资源加载过多,初始页面加载时间较长
- 不利于 SEO,这个可以通过在服务器渲染来优化
性能优化
代码层面:
- 防抖和节流(resize,scroll,input)。
- 减少回流(重排)和重绘。
- 事件委托。
- css 放 ,js 脚本放 最底部。
- 减少 DOM 操作。
- 按需加载,比如 React 中使用 React.lazy 和 React.Suspense ,通常需要与 webpack 中的 splitChunks 配合。
构建方面:
- 压缩代码文件,在 webpack 中使用 terser-webpack-plugin 压缩 Javascript 代码;使用 css-minimizer-webpack-plugin 压缩 CSS 代码;使用 html-webpack-plugin 压缩 html 代码。
- 开启 gzip 压缩,webpack 中使用 compression-webpack-plugin ,node 作为服务器也要开启,使用 compression。
- 常用的第三方库使用 CDN 服务,在 webpack 中我们要配置 externals,将比如 React, Vue 这种包不打倒最终生成的文件中。而是采用 CDN 服务。
其它:
- 使用 http2。因为解析速度快,头部压缩,多路复用,服务器推送静态资源。
- 使用服务端渲染。
- 图片压缩。
- 使用 http 缓存,比如服务端的响应中添加 Cache-Control / Expires 。
严格模式
- 无法意外创建全局变量
- 会使静默失败的赋值抛出异常
- 视图删除不可删除的属性时会抛出异常
- 要求函数的参数名唯一
- 全局作用域下,this 的值为 undefined
- 捕获了一些常见的编码错误,并抛出异常
- 禁用令人困惑或欠佳的功能
浏览器的垃圾回收机制
- 标记清除:标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁。
- 引用计数:它把对象是否不再需要简化定义为对象有没有其他对象引用到它。如果没有引用指向该对象(引用计数为 0),对象将被垃圾回收机制回收。