call, apply,bind都是改变函数this的指向,而函数运行时候this是取决于调用这个函数的对象
来看一个this的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 var name = "tal" var sex = "boy" var tal = { name: "踏浪" , sex: "男" } function person ( ) { console .log(this .name) console .log(this .sex) } person()
person()函数定义在全局中,相当于window.person(),是由window调用的,所以this这里等同于window
call 1 2 3 4 5 6 7 8 9 10 11 12 13 var name = "tal" var sex = "boy" var tal = { name: "踏浪" , sex: "男" } function person ( ) { console .log(this .name) console .log(this .sex) } person.call(tal)
此时,person的this指向tal,就相当于执行了tal.person();
call实现 1 2 3 4 5 Function .prototype.mycall = function (context ) { context.fn = this context.fn() delete context.fn }
mycall接收一个参数,即一个对象,最终的this指向这个对象。函数内部实现在这个传入的对象中绑定上我们需要执行的这个函数,即context.fn = this一行。最后调用context.fn()。因为我们这样操作修改了传入对象的属性(添加了一个fn属性),所以最后需要删除这个fn属性。这样,第一版的call的实现已经完成。
接下来,让call可以接受参数
1 2 3 4 5 6 7 8 9 10 11 Function .prototype.mycall = function (context ) { var args = []; var len = arguments .length; for (var i = 1 ; i < len; i++) { args.push(arguments [i]); } context.fn = this eval ('context.fn(' + args.join("," ) + ')' ) context.fn(...args); delete context.fn }
因为我们需要在 mycall 调用的时候传递参数,而且参数的个数不确定,所以需要使用 arguments 。同时因为第一个参数以及确定了是我们需要的一个对象,this指向这个对象。所以 arguments 需要从 1 开始。我们用一个数组把需要的东西存放起来。
apply的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 Function .prototype.myapply = function (context ) { var args = []; var array = arguments [1 ]; if ( typeof array !== "object" || !(array instanceof Array ) ) { throw new Error ("The 2'rd args must be Array." ) } for (var i = 0 ; len = array.length, i < len; i++) { args.push('array[' + i + ']' ); } context.fn = this eval ('context.fn(' + args.toString() + ')' ) delete context.fn }
唯一不同的就是apply传递的参数是一个数组,而call是具体的每一项。只需要在参数上面做处理即可。 这里的 arguments[1] 是一个数组了。我们需要对它遍历,并且判断它是不是一个数组。其余的与 call 一样。
bind的实现 原生的bind有两种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var tal = { name: "踏浪" , sex: "男" } function person (age ) { console .log(age) console .log(this .name) console .log(this .sex) } person.bind(tal)(18 ) person.bind(tal, 18 )()
所以。使用bind都需要调用两次,而第一次就是返回一个函数。原函数的参数可以在bind中调用,也可以在第二次运行时候调用。所以,根据调用bind时候传递的参数的个数确定最后是返回那种函数,有了下面的这段代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Function .prototype.mybind = function (context ) { var args = []; var len = arguments .length; for (var i = 1 ; i < len; i++) { args.push(arguments [i]); } context.fn = this if (len === 1 ) { return function ( ) { var sub_args = []; for (var i = 0 ; len = arguments .length, i < len; i++) { sub_args.push('arguments[' + i + ']' ); } eval ('context.fn(' + sub_args.toString() + ')' ) delete context.fn } } else { return function ( ) { var str = 'context.fn(' for (var i = 0 ; len = args.length, i < len; i++) { str += "args[" +i+"]," } var newStr = str.replace(/,$/ , ")" ) eval (newStr) delete context.fn } } }
总结 通过自己实现 call, apply, bind 这三种方法,能够更深刻的理解到这三个函数的原理,同时涉及到的只是点也多:this指向,arguments类数组,每一个对象都要的toSting方法(另一个是valueOf),eval方法的使用(不是滥用,webpack中就使用了这个方法),函数对象可以使用 delete 删除(使用var 定义的无法使用delete删除)。或许你已经明白了,但是代码种东西,还是自己动动手,印象更深刻。