前言
上篇文章因篇幅原因还有一些es6的面试题没有写完,这边文章就时间委托、预获取、和es6等一些面试题进行讲解记录。
【重点】事件委托
事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件;
通过事件处理函数的唯一参数 event
对象;
事件委托可以少写很多代码,却能大大减少dom的操作,可以提高性能;
示例代码(使用了 event.type
属性):
let btn = document.getElementById("myBtn");
let handler = function(event) {
switch(event.type) {
case "click":
console.log("Clicked");
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseout":
event.target.style.backgroundColor = "";
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
【需要理解】“==”和“===”的区别
全等运算符 ===
操作规则:
- 如果两个操作数有不同的类型,它们不是严格相等的
- 如果两个操作数都为 null,则它们是严格相等的
- 如果两个操作数都为 undefined,它们是严格相等的
- 如果一个或两个操作数都是 NaN,它们就不是严格相等的
- 如果两个操作数都为 true 或都为 false,它们是严格相等的
- 如果两个操作数都是 number 1. 类型并且具有相同的值,则它们是严格相等的
- 如果两个操作数都是 string 1. 类型并且具有相同的值,则它们是严格相等的
- 如果两个操作数都引用相同的对象或函数,则它们是严格相等的
- 以上所有其他情况下操作数都不是严格相等的。
相等运算符 ==
操作规则:
- 如果操作数具有相同的类型,可以使用全等
===
运算符的规则 - 如果操作数有不同的类型:
- 如果一个操作数为 null 而另一个 undefined,则它们相等
- 如果一个值是数字,另一个是字符串,先将字符串转换为数字,然后使用转换后的值比较
- 如果一个操作数是布尔值,则将 true 转换为 1,将 false 转换为 0,然后使用转换后的值比较
- 如果一个操作数是一个对象,而另一个操作数是一个数字或字符串,则使用OPCA将该对象转换为原原始值,再使用转换后的值比较
- 在以上的其他情况下,操作数都不相等
JS 中对象到字符串的转换经过如下这些步骤(简称 OPCA 算法):
- 如果方法 valueOf() 存在,则调用它。如果 valueOf() 返回一个原始值,JS 将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。
- 如果方法 toString() 存在,则调用它。如果 toString() 返回一个原始值,JS 将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。需要注意,原始值到字符串的转换。
- 否则,JS 无法从 toString() 或 valueOf() 获得一个原始值,它将抛出一个 TypeError:不能将对象转换为原始值 异常
参考链接
【重点】如何判断数组
Array.isArray()
```JavaScript console.log(Array.isArray([1, 2, 4]))
* `Object.prototype.toString.call(obj) == [object Array]`
```JavaScript
console.log(Object.prototype.toString.call([1,2,4]) == "[object Array]")
instanceof
console.log([1,2] instanceof Array)
显示转换与隐式转换值
显示转换一般指使用Number、String和Boolean三个构造函数,手动将各种类型的值,转换成数字、字符串或者布尔值。
隐式转换:比如不同类型变量之间的比较:
代码语言:javascript复制console.log({} == 0)
CDN 预获取
DNS-prefetch
(DNS预获取)能在请求资源之前解析域名
当浏览器从(第三方)服务器请求资源时,会先将该域名解析为 IP地址,然后浏览器才能发出请求。DNS 的这一解析过程会导致请求增加延迟,可以通过 DNS 预获取,在请求资源之前解析域名
代码语言:javascript复制<link rel="dns-prefetch" href="https://fonts.googleapis.com/">
原型、原型链
无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向 原型对象)。默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构 造函数。
==实例==与==构造函数原型==之间有直接的==联系==,但==实例==与==构造函数==之间==没有==。
正常的原型链都会终止于 ==Object
的原型对象==; Object
原型的原型是 null
console.log((new Object()).__proto__ == Object.prototype);
其他例子:
代码语言:javascript复制function Person() {}
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Person.prototype.__proto__.constructor === Object); // true
console.log(Person.prototype.__proto__.__proto__ === null); // true
==对象包含 __proto__
指向他的原型对象
prototype
指向原型对象==
function Person(name) {
console.log(name)
}
let kobe = new Person('科比');
console.log(kobe.__proto__ === Person.prototype)
// true
参考链接 参考链接
继承
ES6之前
代码语言:javascript复制function Parent(value) {
this.val = value
}
Parent.prototype.getValue = function () {
console.log(this.val)
}
function Child(value) {
Parent.call(this, value)
}
Child.prototype = new Parent()
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
以上继承的方式核心是利用 call() 方法来继承父类属性,通改变子类原型,让原型指向父类的实例,就可以共享父类的方法了
这种继承方式优点在于构造函数可以传参,不会与父类引用属性共享,可以复用父类的函数,但是也存在一个缺点 就是在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费。
- 在ES6中可以使用
class
去继承
class Parent {
constructor(value) {
this.val = value
}
getValue() {
console.log(this.val)
}
}
class Child extends Parent {
constructor(value) {
super(value)
this.val = value
}
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
class
实现继承的核心在于==使用 extends
== 表明继承自哪个父类,并且在子类构造函数中必须调用 super
,因为这段代码可以看成 Parent.call(this, value)
【重点】promise
Promise.all()
方法 该方法指当所有在可迭代参数中的 promises 已完成,或者第一个传递的 promise(指 reject)失败时,返回 promise。但是当其中任何一个被拒绝的话。主Promise.all([..])就会立即被拒绝,并丢弃来自其他所有promis的全部结果。 Promise.all 里的任务列表[asyncTask(1),asyncTask(2),asyncTask(3)],我们是按照顺序发起的。 但它们是异步的,互相之间并不阻塞,每个任务完成时机是不确定的,尽管如此,所有任务结束之 后,它们的结果仍然是按顺序地映射到resultList里,这样就能和Promise.all里的任务列表[asyncTask(1),asyncTask(2),asyncTask(3)]一一对应起来。Promise.race()
方法 Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。- 期约取消 用“**==取消令牌==**”(==cancel token==);生成的令牌实例提供了一个接口,利用这个接口可以取消期约;同时也提供了一个期约的实例,可以用来触发取消后的操作并求值取消状态
下面是 CancelToken 类的一个基本实例:
代码语言:javascript复制class CancelToken {
constructor(cancelFn) {
this.promise = new Promise((resolve, reject) => {
cancelFn(resolve);
});
}
}
【重要】事件循环机制(event loop),微任务、宏任务,执行顺序
JavaScript 语言是单线程,单线程就意味着,所有任务需要排队;javascript引擎实现非阻塞的关键就是 事件循环机制 event loop
所有任务可以分为两种:
- 同步任务:在主线程上排队执行的任务,只有前一个任务执行完才能执行后一个任务,==比如Promise 声明里面的代码,Promise.resolve() 或者 Promise.reject()==
- 异步任务:主线程会先挂起(pending)异步任务,进入“任务队列”(task queue),在该异步任务返回结果的时候再根据一定规则去执行相应的回调
只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
事件和回调函数
所谓"回调函数"(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
微任务宏任务
- 宏任务:
- 整体的Script代码
- setInterval()
- setTimeout()
- setImmediate()
- Promise 声明里面的代码
- Promise.resolve() 或者 Promise.reject()里面的代码
- 微任务:
- Promise 的 .then()
- process.nextTick
- new MutaionObserve()
==先执行执行栈上的同步任务(某些宏任务),执行完执行栈上的同步任务后再执行任务队列里面的微任务,然后再执行任务队列的宏任务,然后一直循环==
async/await 的执行
async 和 await 其实就是 Generator 和 Promise 的语法糖。 async 函数和普通 函数没有什么不同,他只是表示这个函数里有异步操作的方法,并返回一个 Promise 对象
代码语言:javascript复制async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
// Promise 写法
async function async1() {
console.log("async1 start");
Promise.resolve(async2()).then(() => console.log("async1 end"));
}
Node.js中微任务与其他微任务的不同
Node.js还提供了另外两个与"任务队列"有关的方法:process.nextTick
和setImmediate
。
process.nextTick
方法可以在当前“执行栈”的尾部——下一次Event Loop(主线程读取“任务队列”)之前——触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。setImmediate
方法则是在当前“任务队列”的尾部添加事件,也即是说,它指定的任务总是在下一次Event Loop
时执行。
参考博客阮一峰
【重要】从输入URL到页面展示详细?
- DNS 解析
- TCP 连接(三次握手)
- 浏览器处理请求并且回复http报文
- 浏览器解析渲染页面
- TCP 断开连接(四次挥手)
参考答案1 参考答案2
【重要】promise中reject和catch的问题
代码语言:javascript复制promise1 = new Promise((resolve, reject) => {
reject("错误信息")
})
promise1.then(res => {
console.log(res)
}, err => {
console.log(err)
}).catch(error => {
console.log(error "catch")
})
// 输出:错误信息
reject后的东西一定会进入then中的第二个回调,如果then中没有写第二个回调,则进入catch
js中浮点类型的计算
代码语言:javascript复制0.1 0.2 > 0.3
JS 采用 IEEE 754双精度版本
什么是链表,链表和数组有什么区别?
- 数据的内存是连续储存的
- 元素可能存储在内存的任意地方,链表创建一个指针指向相应的数据
写在最后
我是 AndyHu,目前暂时是一枚前端搬砖工程师。
文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注呀