【call、apply、bind、new手写实现】

手写callapply

手写 call

call() :在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

let foo = {
    name: "aq"
}

function getName() {
    console.log(this.name);
}

getName(); // undefined

getName.call(foo) // 'aq'

call() 实现的功能如下:

  • 改变this的指向,指到了getName
  • getName 函数执行了

第一步

上述方式等同于:

let foo = {
    name: "aq",
    getName: function () {
        console.log(this.name)
    }
}

foo.getName() // 'aq'

这个时候getName指向了foo,但是这样却给foo本身添加了一个属性,但是我们可以用delete删除。
所以可以将步骤分为:

  1. 将该函数设置为对象的属性;
  2. 执行该函数;
  3. 删除该函数。

所以可以实现一版:

// 第一版
Function.prototype.call2 = function(context) {
    // 首先要获取调用call的函数,用this可以获取
    context.fn = this;
    context.fn();
    delete context.fn;
}

// 测试一下
 let foo = {
    name: 'aq'
};

function getName() {
    console.log(this.name);
}

getName.call2(foo); // 'aq'

第二步

call 除了可以指定 this 还可以指定参数

// 第二版
Function.prototype.call2 = function(context) {
    // 首先要获取调用call的函数,用this可以获取
    context.fn = this;
    let arg = [...arguments].slice(1)
    context.fn(...arg);
    delete context.fn;
}

// 测试一下
 let foo = {
    name: 'aq'
};

function getName(age,gender) {
    console.log(age);
    console.log(gender);
    console.log(this.name);

}

getName.call2(foo,19,1); // 19 1 'aq'

第三步

  1. 当指定的对象为nullundefined的情况下
var name = 'aq'

function getName(){
    console.log(this.name);
}

getName.call(null) // 'aq'
getName.call(undefined) // 'aq'
  1. 被调用函数有返回值的情况下

function getName(){
    return {
        text: "我有返回值"
    }
}

getName.call() // {text: '我有返回值'}

所以最后终写法:

    Function.prototype.call2 = function(context,...args) {
        if(typeof context === 'undefined' || context === null){
            context = window
        }
        let symbolFun = Symbol();
        context[symbolFun] = this;
        let result = context[symbolFun](...args);
        delete context[symbolFun];
        return result
    }

手写 apply

apply 的实现跟 call 类似,只是入参不一样,apply为数组

所以最后终写法:

    Function.prototype.call2 = function(context,args) {
        if(typeof context === 'undefined' || context === null){
            context = window
        }
        let symbolFun = Symbol();
        context[symbolFun] = this;
        let result = context[symbolFun](...args);
        delete context[symbolFun];
        return result
    }

手写 bind

Function 实例的 bind() 方法创建一个新函数,当调用该新函数时,它会调用原始函数并将其this关键字设置为给定的值,同时,还可以传入一系列指定的参数,这些参数会插入到调用新函数时传入的参数的前面。

由此可见bind函数的两个特点:

  • 返回一个函数
  • 可以传入参数

返回函数模拟实现

var foo = {
    value: 1
}

function bar () {
    console.log(this.value)
}

var bindFoo = bar.bind(foo);

bindFoo(); // 1

关于this的指向,我们可以利用callapply实现

// 版本一

Function.prototype.bind2 = function (context) {
    let self = this;
    // 当返回函数给外部之后 外部执行的 this就会被改变 所以这里要保存this
    // 被绑定函数可能会有返回值 这里将执行结果返回出去
    return function () {
        return self.apply(context)
    }
}

传参模拟实现


var foo = {
    value: 1
};

function bar(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);

}

var bindFoo = bar.bind(foo, 'daisy');
bindFoo('18');
// 1
// daisy
// 18

当需要传 name 和 age 两个参数时,可以在 bind 的时候,只传一个 name,在执行返回的函数的时候,再传另一个参数 age。
这里如果不适用rest,使用arguments进行处理:

// 第二版
Function.prototype.bind2 = function (context) {

    var self = this;
    // 获取bind2函数从第二个参数到最后一个参数
    var args = Array.prototype.slice.call(arguments, 1);

    return function () {
        // 这个时候的arguments是指bind返回的函数传入的参数
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(context, args.concat(bindArgs));
    }
}

构造函数效果实现

bind 还有一个特点:
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的this值被忽略,同时调用时的参数被提供给模拟函数。

也就是说当bind返回的函数作为构造函数的时候,bind指定的this值会失效,但是传入的参数依然生效。举个栗子:

var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}

bar.prototype.friend = 'kevin';

var bindFoo = bar.bind(foo, 'daisy');

var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin

尽管在全局和 foo 中都声明了 value 值,最后依然返回了 undefined,说明绑定的 this 失效了

// 第三版
Function.prototype.bind2 = function (context,...args) {
    let self = this;

    let fBind =  function (...args2) {
        // 如果调用这个函数是构造函数实例调用的 那么 this 指向 该实例 否则 this 指向 window
        return self.apply(this instanceof fBind ? this: context ,[...args,...args2])
    }

    // 这里再将原型赋值给你新创建的函数
    fBind.prototype = this.prototype

    return fBind
}

构造函数效果优化实现

我们直接将 fBind.prototype = this.prototype ,假如我直接修改 fBind.prototype, 这意味着绑定函数的 prototype 也是会被修改的。所以这里我们用一个空的构造函数中转一下

// 第四版
Function.prototype.bind2 = function (context,...args) {
    let self = this;

    let middle = function () {}

    let fBind =  function (...args2) {
        // 如果调用这个函数是构造函数实例调用的 那么 this 指向 该实例 否则 this 指向 window
        return self.apply(this instanceof fBind ? this: context ,[...args,...args2])
    }

    middle.prototype = this.prototype
    fBind.prototype = new middle();

    return fBind
}

最终版

调用 bind 的不是函数时,提示错误:

Function.prototype.bind2 = function (context,...args) {
    if (typeof this !== "function") {
        throw new Error("提示错误信息");
    }
    let self = this;
    let middle = function () {}
    let fBind =  function (...args2) {
        return self.apply(this instanceof fBind ? this: context ,[...args,...args2])
    }
    middle.prototype = this.prototype
    fBind.prototype = new middle();
    return fBind
}

手写 new

new运算符允许开发人员创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

根据MDN的描述,当使用new关键字调用函数时,该函数将被用作构造函数。new将执行以下操作:

  1. 创建一个空的简单 JavaScript 对象。为方便起见,我们称之为 newInstance。
  2. 如果构造函数的 prototype 属性是一个对象,则将 newInstance 的 [[Prototype]] 指向构造函数的 prototype 属性,否则 newInstance 将保持为一个普通对象,其 [[Prototype]] 为
    Object.prototype。
  3. 使用给定参数执行构造函数,并将 newInstance 绑定为 this 的上下文(换句话说,在构造函数中的所有 this 引用都指向 newInstance)。
  4. 如果构造函数返回非原始值,则该返回值成为整个 new 表达式的结果。否则,如果构造函数未返回任何值或返回了一个原始值,则返回 newInstance。(通常构造函数不返回值,但可以选择返回值,以覆盖正常的对象创建过程。)
function objectFactory() {
    // 1.首先创建一个空的JavaScript对象
    let newInstance = Object.create();

    // 将构造函数提取出来 arguments 里面剩余的就是参数
    let Constructor = [].shift.call(arguments);

    // 2.将 newInstance 的 [[prototype]] (__proto__) 属性指向 构造函数的prototype 
    Object.setPrototypeOf(newInstance, Constructor.prototype);

    // 3.使用指定参数执行构造函数 将newInstance绑定为this
    let result = Constructor.apply(newInstance,arguments)

    // 4.判断构造函数是否返回了对象 否则返回newInstance
    return typeof result === 'object' ? result : newInstance

}

测试一下

function objectFactory() {
    // 1.首先创建一个空的JavaScript对象
    let newInstance = Object.create(null);

    // 将构造函数提取出来 arguments 里面剩余的就是参数
    let Constructor = [].shift.call(arguments);

    // 2.将 newInstance 的 [[prototype]] (__proto__) 属性指向 构造函数的prototype 
    Object.setPrototypeOf(newInstance, Constructor.prototype);

    // 3.使用指定参数执行构造函数 将newInstance绑定为this
    let result = Constructor.apply(newInstance,arguments)

    // 4.判断构造函数是否返回了对象 否则返回newInstance
    return typeof result === 'object' ? result : newInstance

}

function foo (name,age) {
    this.name = name;
    this.age = age
    // return {}
}

foo.prototype.getInfo = function () {
    console.log(this.name + '今年' + this.age + '岁');
}

let obj = objectFactory(foo,'敏敏',18);
obj.getInfo(); // 敏敏今年18岁
console.log(obj); // foo { name: '敏敏', age: 18 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值