JavaScript学习之call、apply、bind的理解与实现

JavaScript学习之call、apply、bind的理解与实现

学习冴羽的JavaScript深入之call和apply的模拟实现和JavaScript深入之bind的模拟实现的笔记

call

fun.call(thisArg[,arg1[,arg2,]]);
  • call是属于所有Function的方法,也就是Function.prototype.call
  • call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。
  • call() 方法可以指定若干个参数

使用call实现继承

  function Animal( name,type )
        {
            this.name = name
            this.type = type
        }
        function Cat( name,type,age )
        {
            Animal.call( this,name,type )
            this.age = age
            this.say = function ()
            {
                console.log( `i am ${ this.name }` );
            }
        }
        var cat = new Cat( 'lily','miao',2 )
        cat.say()

实现call()

第一步:实现改变this
//第一版
        Function.prototype.call2 = function ( context )
        {
            // 首先要获取调用call的函数,用this可以获取
            context.fn = this; // foo.fn = bar
            context.fn();			// foo.fn()
            delete context.fn;//删除 foo.fn 方法
        }

        //测试一下
        var foo = {
            value: 1
        };

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

        bar.call2( foo ); // 1

第二步:有参数怎么处理呢?

在执行call的时候是可以传入参数的,但是每次传的参数的个数都是不同的,那该怎么办呢?

这是我们可以使用 arguments ,从其中取参数

arguments 是一个对应于传递给函数的参数的类数组对象。
// 通过循环的方式获取arguments中的参数,并push到args中
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + ']');
}

现在 参数我们已经准备好了,怎么把它们传给 context.fn() 呢?

context.fn(args.join(,))
// 这种方式是不可以的!

那要怎么办呢? 我们可以通过额eval() 这个方法实现
eval() 是什么??
eval() 函数作用是:可计算某个字符串,并执行其中的的 JavaScript 代码。

所以 第二步实现来咯
// 第二版
Function.prototype.call2 = function(context) {
    context.fn = this;
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
    eval('context.fn(' + args +')');
    delete context.fn;
}

// 测试一下
var foo = {
    value: 1
};

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

bar.call2(foo, 'kevin', 18); 
// kevin
// 18
// 1
第三步:还有一些小问题

这是代码基本实现差不多了,但是还有两个小问题

  1. 当fn.call(null) 传过去的 this 是 null ,此时应视为指向的是window
  2. 当 fn 方法有返回值的时候

根据以上两个问题,我们的第三步来了!

 Function.prototype.call2 = function ( context )
        {
            var context = context || window;
            context.fn = this;

            var args = [];
            for ( var i = 1,len = arguments.length; i < len; i++ )
            {
                args.push( 'arguments[' + i + ']' );
            }
            //   eval() 用于计算某个字符串并且执行计算完之后的代码
            var result = eval( 'context.fn(' + args + ')' );
            delete context.fn
            return result;
        }

        // 测试一下
        var value = 2;

        var obj = {
            value: 1
        }

        function bar( name,age )
        {
            console.log( this.value );
            return {
                value: this.value,
                name: name,
                age: age
            }
        }
        function bar1()
        {
            console.log( this.value );
        }
        function bar2()
        {
            console.log( this.value );
        }
        console.log( bar.call2( obj,'kevin',18 ) );
				// 1
				// Object {
				//    value: 1,
				//    name: 'kevin',
				//    age: 18
				// }	
        console.log( bar1.call2( obj ) ); //1
        bar2.call( null ); // 2

好了,到此为止 call 的实现完成了!!!

apply

apply 和 call 基本类似。
不同点是:apply只能传递this和一个参数,call 可以传递多个参数

实现 apply

Function.prototype.apply = function (context) {
    var context = Object(context) || window;
    context.fn = this;
    var result = eval('context.fn(' + arguments[1] + ')')
    delete context.fn
    return result;
}

bind

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

返回函数的实现

先举个例子
   var foo = {
            value: 1
        };
   function bar() {
        console.log( this.value );
    }
   bar.bind( foo )()

bind() 方法 执行后会创建一个新函数并返回这个新函数,bind第一个参数 foo 会被当做新函数运行时的this,之后第参数会被认为是新函数的参数。

首先,需自己写一个apply或者call,不用call 和 apply 实现bind

简单版的实现bind 可以使用call 和 apply,我们在这里不用这两个方法实现 bind,这里写一个apply,因为apply的参数只能是一个,所以比较简单。

 Function.prototype.apply1 = function ( context )
        {
            context.fn = this
            var res = eval( `context.fn(${ arguments[ 1 ] })` )
            console.log( this )
            delete context.fn
            return res
        }
实现这一步
Function.prototype.bind2 = function (context) {
    var self = this;
    return function () {
        return self.apply1(context);
    }
}

bind传参的实现

先举一个例子
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

函数bar 需要两个参数,我们在执行bind的时候传递进去一个 name,然后在执行返回的函数bindFoo的时候也可以传参数!神奇!太神奇了!
那我们要怎么实现呢???

实现bind传参来了
   		var foo = {
            value: 1
        };
        function bar( name, age )
        {
            console.log( this.value );
            console.log( a,b,c )
        }
//------------------------------------------------------- 
			Function.prototype.bind2 = function ( context )
        {
            var self = this;
            var args = []
            for ( let i = 1; i < arguments.length; i++ )
            {
                args.push( arguments[ i ] )
            }
            return function ()
            {
                return self.apply1( context,args.concat( [ ...arguments ] ) );
            }

        }
        console.log( bar.bind2( foo,'daisy' )( 18 ) )

通过将执行bind() 时穿过去的参数与执行返回的函数传过去的参数相结合,然后调用apply1() 这样实现bind的参数传递。

构造函数效果的实现

最难的部分来咯!!!
bind方法返回的函数也可以作为一个构造函数使用!
这时,bind方法传递给他的 this 就无效了,但是参数依然是有效地,而这时 this 指向的就是实例出来的对象了。

先举个例子

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 --->this 已经不指向 foo了
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
实现构造函数效果来了
			Function.prototype.bind2 = function ( context )
        {
            var self = this;
            var args = []
            for ( let i = 1; i < arguments.length; i++ )
            {
                args.push( arguments[ i ] )
            }
            var myFun = function ()
            {
        // 当作为构造函数时,this 指向实例对象,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
        // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
              return self.apply1( this instanceof myFun ? this : context,args.concat( [ ...arguments ] ) );
            }
            myFun.prototype = this.prototype
            return myFun
        }
构造函数效果的优化

我们通过 myFun.prototype = this.prototype 修改返回函数的 prototype 为绑定函数的 prototype,这种方式虽然可以,但是一旦修改myFun的prototype 就会修改绑定函数的 prototype,会污染绑定函数的prototype
所以,优化一下:

	Function.prototype.bind2 = function ( context )
        {
            var self = this;
            var args = []
            for ( let i = 1; i < arguments.length; i++ )
            {
                args.push( arguments[ i ] )
            }
    				var funFather = function (){}
            var myFun = function ()
            {
        // 当作为构造函数时,this 指向实例对象,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
        // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
              return self.apply1( this instanceof funFather ? this : context,args.concat( [ ...arguments ] ) );
            }
            funFather.prototype = this.prototype
    				muFun.prototype = new funFather()
            return myFun
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值