js面试跳跳题二

2023-06-27 14:30:50 浏览数 (1)

前言

上篇文章因篇幅原因还有一些es6的面试题没有写完,这边文章就时间委托、预获取、和es6等一些面试题进行讲解记录。

【重点】事件委托

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件;

通过事件处理函数的唯一参数 event 对象;

事件委托可以少写很多代码,却能大大减少dom的操作,可以提高性能;

示例代码(使用了 event.type 属性):

代码语言:javascript复制
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;

【需要理解】“==”和“===”的区别

全等运算符 === 操作规则:
  1. 如果两个操作数有不同的类型,它们不是严格相等的
  2. 如果两个操作数都为 null,则它们是严格相等的
  3. 如果两个操作数都为 undefined,它们是严格相等的
  4. 如果一个或两个操作数都是 NaN,它们就不是严格相等的
  5. 如果两个操作数都为 true 或都为 false,它们是严格相等的
  6. 如果两个操作数都是 number 1. 类型并且具有相同的值,则它们是严格相等的
  7. 如果两个操作数都是 string 1. 类型并且具有相同的值,则它们是严格相等的
  8. 如果两个操作数都引用相同的对象或函数,则它们是严格相等的
  9. 以上所有其他情况下操作数都不是严格相等的。
相等运算符 == 操作规则:
  1. 如果操作数具有相同的类型,可以使用全等 === 运算符的规则
  2. 如果操作数有不同的类型:
    1. 如果一个操作数为 null 而另一个 undefined,则它们相等
    2. 如果一个值是数字,另一个是字符串,先将字符串转换为数字,然后使用转换后的值比较
    3. 如果一个操作数是布尔值,则将 true 转换为 1,将 false 转换为 0,然后使用转换后的值比较
    4. 如果一个操作数是一个对象,而另一个操作数是一个数字或字符串,则使用OPCA将该对象转换为原原始值,再使用转换后的值比较
  3. 在以上的其他情况下,操作数都不相等
JS 中对象到字符串的转换经过如下这些步骤(简称 OPCA 算法):
  1. 如果方法 valueOf() 存在,则调用它。如果 valueOf() 返回一个原始值,JS 将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。
  2. 如果方法 toString() 存在,则调用它。如果 toString() 返回一个原始值,JS 将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。需要注意,原始值到字符串的转换。
  3. 否则,JS 无法从 toString() 或 valueOf() 获得一个原始值,它将抛出一个 TypeError:不能将对象转换为原始值 异常

参考链接

【重点】如何判断数组

  • Array.isArray() ```JavaScript console.log(Array.isArray([1, 2, 4]))
代码语言:javascript复制
* `Object.prototype.toString.call(obj) == [object Array]`
```JavaScript
console.log(Object.prototype.toString.call([1,2,4]) == "[object Array]")

instanceof

代码语言:javascript复制
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

代码语言:javascript复制
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 指向原型对象==

代码语言:javascript复制
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 去继承
代码语言:javascript复制
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.nextTicksetImmediate

  • 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,目前暂时是一枚前端搬砖工程师。

文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注呀

0 人点赞