JS手动实现call() , apply() , bind()

一、手动实现call()

1、我们想要实现call(),必须要了解call的特点:

		1、首先call()位于Function.prototype中,所以我们定义myCall()的时候,也在此处定义
		2、call()的作用是改变this指向,将call()中第一个参数的this指向赋值给调用它的函数
		3、若是call()中的第一个参数为null,或者undefined,且这个函数处于非严格模式,则会将null或者undefined自动替换为全局对象
		4、call()中除了第一个参数外,还接收一个参数列表
		5、call()的返回值是当前调用call()的函数的返回值,若是当前调用的函数没有返回值,则返回undefined

2、实现call:

Function.prototype.myCall = function(context, ...args) {
    if(this === Function.prototype) {//判断当前this是否为函数
        return undefined;//用于防止 Function.prototype.myCall()  直接调用
    }

    // console.log(this);//此处的this指向foo,因为是foo调用的(隐式绑定规则)

    context? context : window    //context为可选的参数,如果不传的话默认上下文为window

    const fn = Symbol();   //为context创建一个Symbol属性
    context[fn] = this;   //将当前调用myCall的函数赋值给这个属性
    console.log(context[fn]);

    const result = context[fn](...args); //处理参数,传入第一个参数后的其余参数,这里可以理解为obj.foo(...args)

    delete context[fn]  //调用函数后即删除该Symbol属性
    return result
}

3、对自己创建的myCall的应用:

var obj = {
    a:1
}

function foo(b,c) {
    // console.log(this);//若是不用myCall改变this指向,this指向全局(默认绑定规则),但是用myCall改变this指向后,现在的this指向obj
    return  this.a + b + c
}

let bar = function(b,c) {
    // console.log(this);//this指向全局变量,因为bar在全局中调用
    return foo.myCall(obj,b,c)
}

let b = bar(2,3)

console.log(b);

二、手动实现apply()

1、apply与call唯一的区别就是,参数不一样,apply接收类数组或者数组

2、实现apply():

Function.prototype.myApply = function (context , args) {
    if (this === Function.prototype) {
      return undefined; // 用于防止 Function.prototype.myCall() 直接调用
    }
    
context? context : window

    const fn = Symbol();
    context[fn] = this;
    let result;
    if (Array.isArray(args)) { //Array.isArray()用于确定传递的值是否为一个Array
      result = context[fn](...args); 
    } else {
      result = context[fn]();
    }
    delete context[fn];
    return result;
  }

三、手动实现bind()bind借鉴于此文

一句话介绍bind:
bind()方法会创建一个新函数,当这个新函数被调用时,bind()的第一个参数将作为它运行时的this,之后一序列参数将会在传递的实参前传入作为它的参数

bind函数的两个特点:返回一个函数,可以传入参数

1、返回函数的模拟实现

var foo = {
    value : 1
}

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

//返回了一个函数
var bindFoo = bar.bind(foo)

bindFoo()

我们来写第一版:

Function.prototype.myBind = function(context) {
    var self = this
    return function() {
        return self.apply(context)
    }
}

这里的return self.apply(context),是考虑到绑定函数可能是有返回值的

var foo = {
    value : 1
}

function bar() {
    return this.value
}

var bindFoo = bar.myBind(foo)//若是myBind中没有上述的返回值,则娶不到value的值

console.log(bindFoo());//1

2、传参的模拟实现

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

第二版 :

Function.prototype.myBind = function (context) {
    var self = this
    //获取myBind函数从第二个参数到最后一个参数
    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))
    }
}

3、构造函数的模拟实现

bind还有一个特点:

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

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

var value = 2
var foo = {
    value : 1
}

function bar(name,age) {
    this.habit = 'shopping'
    console.log(this.value);//undefined   //此时的this会失效
    console.log(name);//daisy  
    console.log(age);//18  
}

bar.prototype.friend = 'kk'

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

var obj = new bindFoo('18')//此时的this已经指向了obj

 

console.log(obj.habit);
// shopping
console.log(obj.friend);
//kk

所以我们可以更改下原型,来我们实现第三版:

Function.prototype.myBind = function(context) {
    var self = this
    var args = Array.prototype.slice.call(arguments,1)

    var fBound = function() {
        var bindArgs = Array.prototype.slice.call(arguments)
        //当作为构造函数的时候,this指向实例,此时结果为true,将绑定函数的this指向该实例,可以让实例获得来自绑定函数的值
        //以上面的demo为例,如果改成‘this instanceof fBound ? null : context’,实例只是一个空对象,将null改成this
        //当作为普通函数的时候,this指向window,此时结果为false,将绑定函数的this指向context
        return self.apply(this instanceof fBound? this : context,args.concat(bindArgs))
    }
    //修改返回函数的prototype为绑定函数的prototype,实例就可以继承函数的原型中的值
    fBound.prototype = this.prototype
    return fBound
}

4、构造函数效果的优化实现

但是在第三版的写法中,我们直接将 fBound.prototype = this.prototype,我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转:

Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        //当使用构造函数调用的时候,this指向的是实例对象,因为新创建的实例对象是空的,所以可以新建一个函数fNOP代替这个空的实例对象,所以只需要判断这个this是否指向这个fNOP就行
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值