likes
comments
collection
share

深入理解Javascript面试题

作者站长头像
站长
· 阅读数 20

我像麋鹿一样在林荫中走着,为着自己的香气而发狂;夜晚是五月正中的夜晚,清风是南国的清风; 我迷路了,我游荡着,我寻求那得不到的东西,我得到了我所没有寻求的东西。 —— 泰戈尔

原型链

原型链 = 原型 + 链式连接

先说原型,比如 const arr = new Array(),这样一串代码,在这个 arr 上有个 __proto__ 属性和 Array 中的 prototype 指向同一个位置,所以关系为 arr.__proto__ === Array.prototype,所以可以说 arr 的原型是 Array.prototype

接下来说原型链,也就是将多个原型链式连接。每个对象的原型都是 Object.prototype,因为 Array.prototype 也是对象,所以关系为 Array.prototype.__proto__ === Object.prototype,所以可以说 Array.prototype 的原型是 Object.prototype

这样他们之间就形成了一个链式的关系,所以叫原型链。

const arr = new Array()

Object.getPrototypeOf(arr) === Array.prototype // true

Object.getPrototypeOf(Array.prototype) === Object.prototype // true

Object.getPrototypeOf(Object.prototype) === null // true

将上述代码图形化就是:

深入理解Javascript面试题

细致了解:继承与原型链 - JavaScript | MDN

继承

背代码就行,举例的方法,说代码实现。

1.原型继承

function Animal(color){
	this.color = color
}
Animal.prototype.move = function(){}

function Dog(color, name){
    Animal.call(this, color)
    this.name = name
}

function temp(){}
temp.prototype = Animal.prototype
Dog.prototype = new temp()
Dog.prototype.constructor = Dog

let dog = new Dog("白黄黑", "小短腿")

2.使用class

class Animal{
    constructor(leg){
        this.leg = leg
    }
    run(){}
}

class Dog extends Animal{
    constructor(name){
        super(4)
        this.name = name
    }
    say(){
        console.log("xxx")
    }
}

细致了解:JavaScript深入之继承的多种方式和优缺点

闭包

闭包 是由 函数 + 自由变量 组成的。比如:

var a = 1;

function foo() {
    console.log(a);
}

foo();

foo 函数能访问变量 a,但 a 既不是函数的参数,也不是局部变量,所以该变量就是自由变量,那么 函数foofoo访问的自由变量a 构成了闭包

细致了解:JavaScript深入之闭包

this

全局执行上下文中的 this 指向是 window 对象,函数执行上下文中的 this 分多种情况:

  1. 在函数调用中,thiscall、apply、bind 的第一个参数
  2. 使用对象调用函数中的方法,this 指向该对象本身
  3. 在全局环境中调用一个函数,函数内部 this 指向全局 window

细致了解:this 的值到底是什么?一次说清楚

instanceof 和 typeof

  • typeof

typeof 一般用来判断一个变量的类型,主要判断 number、string、boolean、symbol、undefined、object、function,但是这里比较特殊的就是 object,它只能判断出是 object 而不能知道是哪种,这时候就需要使用 instanceof 来做细致判断了。有一个特殊的变量值就是 null,它使用 typeof 也会返回一个 object,这是因为 js底层在存储变量的时候,会在机器码低位1-3位储存类型信息,对象类型信息是 000,而 null 这个特殊值的存储形式均为0,所以就被当成了对象。

  • instanceof

instanceof 的作用就是用来判断某个实例是否属于某种类型,使用方式是 a instanceof b,实现原理是 遍历左边变量a的原型链,查找找到右边变量b的prototype,查找失败就 return false,也就是说一直在遍历左边变量的 __proto__ 值,判断是否和右边变量的 prototype 全等。

call、apply 和 bind

先看使用方式:

fn.call(this, ...args)

fn.apply(this, [...args])

fn.bind(this, ...args)()

通过使用方式就可以一目了然的知道这三个函数之间的区别了:

  1. call 函数的传参,从第二个形参开始,使用逗号分隔;apply 函数的传参,也是第二个形参,但是它第二形参的类型是数组,数组里面的元素是参数;bind 函数的传参和 call 的一样;
  2. call & apply 都是直接调用函数;bind 是返回一个改变了执行上下文的新函数;

三个函数之间的相同点:

  1. 主要作用都是用来改变 this指向 也就是改变函数执行时上下文

细致了解:

「干货」细说 call、apply 以及 bind 的区别和用法

JavaScript深入之bind的模拟实现

JavaScript深入之call和apply的模拟实现

柯里化

柯里化 是一种将使用多个参数的函数转化成一系列使用单个参数的函数的技术

function curry(fn, length) {
  length = length || fn.length;

  var slice = Array.prototype.slice;

  return function () {
    // 调用这个返回函数的参数的长度是否小于转化前的函数的参数总个数
    if (arguments.length < length) {
      // 在传参没有完成的情况下,继续返回函数,等待传参
      var combined = [fn].concat(slice.call(arguments));
      return curry(sub_curry.apply(this, combined), length - arguments.length);
    } else {
      // 参数都传递完毕,执行
      return fn.apply(this, arguments);
    }
  };
}

// 这里的 fn 是 apply(this, [..args]) ...args 中的第一个参数
function sub_curry(fn) {
  var args = [].slice.call(arguments, 1);
  return function () {
    // 把之前的参数和当前传入的参数,合并成新数组
    return fn.apply(this, args.concat([].slice.call(arguments)));
  };
}

测试:

var fn = curry(function(a, b, c) {
    return [a, b, c];
});

fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]

细致了解:JavaScript专题之函数柯里化

new 操作符

使用 new 操作符时,会发生以下步骤:

  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行 [[ 原型 ]] 连接。
  3. 这个新对象会绑定到函数调用的 this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。

实现:

function objectFactory() {
    // 创建一个全新对象
    var obj = new Object(), 
    Constructor = [].shift.call(arguments);
    
    // 这个新对象执行 原型连接,Constructor 是传入的对象,也就是相当于 new A() 中的 A函数
    obj.__proto__ = Constructor.prototype;

    // 将新对象绑定到函数调用的 this
    var ret = Constructor.apply(obj, arguments);
    
    // 如果函数没有返回其他对象,那么返回这个新对象
    return typeof ret === 'object' ? ret : obj;

};

细致了解:JavaScript深入之new的模拟实现

事件循环机制

事件循环就是 Event Loop,首先我们知道 JavaScript 是一门单线程非阻塞的脚本语言,这个 非阻塞 的特点就是由 Event Loop 来实现的。js引擎在执行js代码时,分为两种情况,一种是 同步代码,另一种是 异步代码;它会将同步代码按顺序加入到 执行栈 中执行,执行的过程就是一个 压栈和弹栈 的过程,直到将栈中执行完毕结束。但是往往代码不完全是同步的,遇到异步时,js并不会一直等待它的返回结果,而是将这个事件挂起,继续执行其他任务,当异步返回结果后,并不会立即执行,而是将它放入 事件队列 中,等待执行栈中的任务完毕,主线程处于闲置状态,就会去查找 事件队列 中有没有可执行事件,如果有,将该事件推入到执行栈,然后执行同步代码,以此反复,形成一个循环,称为事件循环

细致了解:详解JavaScript中的Event Loop(事件循环)机制

Promise 原理

  1. promise有三种状态,Peding(等待),Fulfilled(执行),Rejected(拒绝)。(⚠️:promise状态是不可逆的,且只有两种变化:P -> F, P -> R)。
  2. promise可以进行链式调用promise.then().then(),可以知道在调用then方法后返回一个新的promise实例,上一个回调函数返回的结果会被返回给下一个promise,这样实现了链式调用。
const p = new Promise((resolve, reject) => {
    return resolve(1)
})
p.then((res) => {
    console.log("one then res: ", res);
    return 2
}).then((res) => {
    console.log("two then res: ", res);
})

/*
one then res:  1
two then res:  2
*/

generator 原理

首先,看下generator基础用法

function* example() {  
    yield 1;  
    yield 2;  
    yield 3;
   }
var iter=example();
iter.next();//{value:1,done:false}
iter.next();//{value:2,done:false}
iter.next();//{value:3,done:false}
iter.next();//{value:undefined,done:true}

在分析这段代码执行原理前,先了解下线程协程,协程比线程更加轻量级,可以看作是协程是跑在线程上的任务,一个线程可以有多个协程,但是每次只能同时执行一个协程,比如执行协程A,需要执行B了,要先停止A的执行,控制权转交给B,B开始执行。

好了,然后请记住上面的话,将它带入到这段代码中,你可以把generator的执行看作就是协程之间的转换

  1. 启动 example,将1返回给其它代码,同时停止 example 的执行,恢复其他代码的执行
  2. 执行第一个 next 函数,执行后,此时就会重新执行 example ,停止其它代码执行
  3. 此刻和第 1 步一样,只有返回值不同,也就是 yield 2 了 ...依此类推,知道没有结果 done为true

图解:generator执行原理图解

async & await 原理

async/await 技术原理就是promisegenerator的应用,底层讲就是微任务和协程应用,它改变了生成器(generator)的缺点,提供了在不阻塞主线程的情况下,使用同步代码访问异步资源的能力。

async函数在执行时也是一个单独的协程,await可以用来暂停这个协程,等待一个promise对象,当await等待这个promise状态为Fulfilledresolve后,V8就会恢复这个协程的执行。

使用下面例子可以清晰看到:

function HaveResolvePromise() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(100)
        })
    })
}

async function getResult() {
    console.log(1);
    const res = await HaveResolvePromise();
    console.log(res);
    console.log(2);
}

console.log(0);
getResult();
console.log(3);

/*
0
1
3
100
2
*/

图解:await/async例子执行图解