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

220 阅读2分钟

bind

  • bind方法可以绑定this指向、参数
  • bind方法返回一个绑定后的函数 (高阶函数)
Function.prototype.bind = function (context) {
    let that = this;
    let bindArge = Array.prototype.slice.call(arguments, 1);
    return function (){
        let args = Array.prototype.slice.call(arguments);
        that.apply(context, bindArge.concat(args));
    }
}

let obj = {
    name: 1
}

function fn(name, age) {
    console.log(this.name, name, age)
}
let bindFn = fn.bind(obj, '猪');
bindFn(8)  //1 '猪' 8
  • 如果绑定的函数被new,当前的函数this就是当前的实例
  • new 出来的的结果,可以找到原有类的原型
Function.prototype.bind = function (context) {
    let that = this;
    let bindArge = Array.prototype.slice.call(arguments, 1);
    function Fn() {};
    function fBind() {
        let args = Array.prototype.slice.call(arguments);
        that.apply(this instanceof fBind ? this : context, bindArge.concat(args));
    }
    Fn.prototype = this.prototype;
    fBind.prototype = new Fn();
    return fBind;
}

let obj = {
    name: 1
}

function fn(name, age) {
    this.name = '自己的';
    console.log(this)   //fn { name: '自己的' }
    console.log(this.name, name, age)  //自己的 猪 9
}
fn.prototype.flag = '2'

let bindFn = fn.bind(obj, '猪');
let instance = new bindFn(9);

console.log(instance.flag);  // 2

new

  • 创建一个实例对象,并且把实例的__proto__指向给这个类的原型对象
  • 如果当前的构造函数返回的是一个引用类型,需要把这个引用类型返回
  • 如果当前的构造函数不是引用类型,返回这个实例对象
function MockNew(){
    let Constructor = [].shift.call(arguments);
    let obj = {};
    obj.__proto__ = Constructor.prototype;

    let r = Constructor.apply(obj, arguments);
    return r instanceof Object ? r : obj;
}

function Animal(type) {
    this.type = type;
    return 1
}
Animal.prototype.say = function (){
    console.log('say');
}


let animal = MockNew(Animal, '哺乳类');
console.log(animal); //Animal { type: '哺乳类' }

call

  • 可以改变当前函数的this指向
  • 当前函数执行
Function.prototype.call = function (context){
    context = context ? Object(context) : window;
    context.fn = this;

    let args = [];
    for (let i = 1; i < arguments.length; i++) {
        //利用toString方法
        args.push('arguments[' + i + ']');
    }
    let r = eval('context.fn(' + args + ')');
    delete context.fn;
    return r;
}

function fn1() {
    console.log(this, arguments);
}
function fn2() {
    console.log(this, 2);
}

fn1.call(fn2)  //fn []
fn1.call.call(fn2) // window 2
fn1.call.call.call(fn2) // window 2
如果是一个call会让call方法执行,并且把call中的this改变成fn2
如果是多个call会让call方法执行,并且把call中的this改变成window

apply

Function.prototype.apply = function(context, args){
    context = context ? Object(context) : window;
    context.fn = this;
    if (!args) {
        return context.fn();
    }
    let r = eval('context.fn('+ args + ')');
    delete context.fn;
    return r;
}

function fn1() {
    console.log(this, arguments);
}
fn1.apply('hello', [1, 2, 3, 4]) // hello [1, 2, 3, 4]

`