深入Javascript原理

2,075 阅读3分钟

call、apply、bind、new、instanceof 实现原理

call 模拟实现

更改函数的 this 指向并执行函数。

Function.prototype.myCall = function(context) {
    var context = context || window
    context.fn = this
    var args = []
    for(var i = 1; i < arguments.length; i++) {
       args.push('arguments[' + i + ']') 
    }
    var result = eval('context.fn(' + args + ')')
    delete context.fn
    return result
}

分析:

  • 首先 context 作为可选参数,如果不传或传 null ,默认上下文为 window
  • 然后给上下文 context 添加方法 fnfn 就是要执行的函数,谁调用了 call() , fn 就是谁。 所以 context.fn = this
  • call 还可以传入多个参数作为调用的函数的参数,可以通过 arguments 对象获取函数的参数。
  • 最后调用函数保存返回值,删除给对象添加的 fn ,将结果返回。

核心就是,通过将函数添加到对象上,然后通过对象调用这个函数,从而使函数中的 this 指向这个对象。

更简洁的写法:

Function.prototype.myCall = function(context, ...rest) {
    var context = context || window 
    context.fn = this
    var result  = context.fn(...rest)
    delete context.fn
    return result
}

apply 模拟实现

apply 实现与 call 类似,区别在于对参数的处理

Function.prototype.myApply = function(context, arr) {
    var context = context || window
    context.fn = this
    var result
    if(!arr) {
        result = context.fn()
    } else {
        var args = []
        for(var i = 0; i < arr.length; i++) {
            args.push('arr[' + i + ']')
        }
        result = eval('context.fn(' + args + ')')
    }
    delete context.fn
    return result
}

简洁:

Function.prototype.myApply = function(context, arr) {
    var context = context || window 
    context.fn = this
    var result 
    if(arr) {
        result = context.fn(...arr)
    }else {
        result = context.fn()
    }
    delete context.fn
    return result 
}

bind 模拟实现

bind 方法返回一个改变 this 指向的函数,bind 第一个参数作为这个函数的 this,后续参数会在函数调用时,作为函数的参数传入,然后才会传入函数的实参。

举个例子:

function foo(name,age) {
    console.log('name:' + name + 'age:' + age)
}
var f =  foo.bind(null,'张三')
f(20)  // name:张三age:20

实现:

Function.prototype.myBind = function(context) {
    var self = this
    var args = [].slice.call(arguments, 1)
    function F() {
        var args2 = [].slice.call(arguments)
        var arglist = args.concat(args2)
        return self.apply(this instanceof F ? this: context, arglist)
    }
    F.prototype = Object.create(self.prototype)
    return F
}

简洁版:

Function.prototype.myBind = function(context) {
    var self = this
    var args = [...arguments].slice(1)
    function F() {
        return self.apply(this instanceof F ? this : context,args.concat(...arguments))
    }
    F.prototype = Object.create(self.prototype)
    return F
}
  • bind 返回的是一个函数, 并在这个函数里面返回 原函数.apply() 实现改变 this 。
  • 需注意的是:如果 bind 返回的函数作为构造函数执行时(new 函数),this 不能改成传入的 context ,this 应该是声明的变量。
  • bind() 传入的除 context 的后续参数,先存起来,在调用由 bind 返回的新函数时 ,向 apply() 传入由原来存的参数和新传入的参数组成的数组(注意顺序)。

new 模拟实现

new 运算符创建了一个用户自定义类型的实例

在执行 new 构造函数() 时,发生了:

  1. 生成一个新对象
  2. 将对象的原型引用(__proto__)指向构造函数的原型
  3. 绑定 this 为该对象,执行构造函数中的内容
  4. 如果函数执行的返回值是对象,就返回这个对象,否则返回之前创建的新对象
function myNew() {
    var constructor = [].shift.call(arguments)
    var obj = Object.create(constructor.prototype)
    var res = constructor.apply(obj, arguments)
    return res instanceof Object ? res: obj
}

ES6 简洁版:

function myNew(constructor, ...rest) {
    let obj = Object.create(constructor.prototype)
    let res = constructor.apply(obj, rest)
    return res instanceof Object ? res : obj
}

instanceof

能正确判断对象的类型,原理是判断构造函数的原型对象是否能在对象的原型链上

function myInstanceof(obj, fn) {
    let prototype = fn.prototype
    let objProto = obj.__proto__
    while(true) {
    if(objProto == null)
        return false
    if(objProto === prototype)
        return true
    objProto = objProto.__proto__
    }
}
  1. 获得构造函数的原型
  2. 获得实例对象的原型
  3. 判断构造函数的原型是否就是实例对象的原型,如果是就返回 true ,不是就让实例对象的原型指向自身的原型继续判断,直到实例对象的原型为 null,返回 false

阅读原文

参考资料:

github.com/mqyqingfeng…

juejin.cn/book/684473…