本文主要探讨JavaScript中的 call、apply、bind是什么?有什么作用?并尝试手写call、apply、bind。
要理解这三个方法,首先要理解js中this
的指向。
一、this指向的五种情况 1.全局上下文中的this 全局上下文中this指向window,这种情况较为简单。
1 console .log(this === window )
2.事件绑定方法中的this 给元素的某个事件行为绑定方法,当事件行为触发,方法执行,方法中的this指向当前元素本身。
1 2 3 4 5 6 7 8 9 10 var a = 'window.a' var body = document .bodyfunction func ( ) { console .log(this ) console .log(this .a) } body.onclick=function ( ) { console .log(this ) func() }
特殊情况:在IE6-8中,基于attachEvent实现的DOM2事件绑定,事件触发,方法中的this指向window
1 2 3 body.attachEvent("onclick" , function ( ) { console .log(this ) })
3.普通函数执行中的this 函数执行(包含自执行函数执行、普通函数执行、对象成员访问调取方法执行等),只需要看函数执行的时候方法名前面是否有.
,有的话,.
前边是谁this就是谁,没有的话this指向window(严格模式下是undefined)
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 (function ( ) { console .log(this ) })() let obj = { f: (function ( ) { console .log(this ) return function ( ) {} })() } obj.f() function func ( ) { console .log(this ) } func() function func ( ) { console .log(this ) } let obj = { f:func } obj.f()
4.构造函数中this指向 构造函数体中的this是当前类的实例
1 2 3 4 function Fun ( ) { console .log(this ) } let f = new Fun()
5.箭头函数中this指向 箭头函数中没有this,它的this是继承自己所在上下文中的this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var a = 'window.a' let obj = { a:'obj.a' , fun: function ( ) { console .log(this ) console .log(this .a) }, arrowFun: () => { console .log(this ) console .log(this .a) } } console .log(obj.fun()) console .log(obj.arrowFun()) obj.arrowFun.call(obj)
二、call、apply、bind是什么?有什么用? 每个函数都是Function
这个类的实例,这三个方法都是Function
的prototype(原型)
上的方法,它们可以改变this的指向。
1.call 和 apply call
和 apply
方法功能是一样的,只是传入的参数形式不一样,作用都是绑定(劫持)一个特定的执行环境,它们的使用方法是:
1 2 func.call(thisArg, arg1, arg2, ...) func.apply(thisArg, [argsArray])
能够看出它们唯一的不同就是 call
方法接受的是一个参数列表,而 apply
方法接受的是一个包含多个参数的数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const o_call = { name: 'chenwuai' } function fn (a,b ) { console .log(this ); console .log(a + b); }; fn.call(o_call,1 ,2 ); var o_apply = { name: 'chenwuai' , } function fn (arr ) { console .log(this ); console .log(arr); } fn.apply(o_apply, [1 ,2 ]);
一些具体的应用
1 2 3 4 5 6 7 8 9 let arr = [1 ,2 ,3 ]Math .max.apply(Math ,arr) function isArray (obj ) { return Object .prototype.toString.call(obj) === '[object Array]' ; } isArray([1 ,2 ,3 ])
2.bind bind
方法创建一个新的函数,在 bind
被调用时,这个新函数的 this
被指定为 bind
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。它的使用方法是:
1 func.bind(thisArg[, arg1[, arg2[, ...]]])
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 this .x = 9 ; var module = { x: 81 , getX: function ( ) { return this .x; } }; module .getX(); var retrieveX = module .getX;retrieveX(); var boundGetX = retrieveX.bind(module );boundGetX();
例如Vue2.X中的 mounted生命周期中的匿名函数
1 2 3 4 5 6 7 8 9 10 11 12 let vm = new Vue({ data(){ return { height:768 } }, mounted(){ window .addEventListener("resize" ,(function ( ) { this .height = document .body.clientHeight }).bind(this )) } })
偏函数例子:
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 function list ( ) { return Array .prototype.slice.call(arguments ); } function addArguments (arg1, arg2 ) { return arg1 + arg2 } var list1 = list(1 , 2 , 3 ); var result1 = addArguments(1 , 2 ); var leadingThirtysevenList = list.bind(null , 37 );var addThirtySeven = addArguments.bind(null , 37 ); var list2 = leadingThirtysevenList(); var list3 = leadingThirtysevenList(1 , 2 , 3 ); var result2 = addThirtySeven(5 ); var result3 = addThirtySeven(5 , 10 );
三、手写简单的call、apply、bind 仅为简单实现,未做边界判断等容错处理。
1.实现call 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 Function .prototype.mycall = function (context,...args ) { const func = this ; if (typeof this !== "function" ) { throw new TypeError ("not a function" ) } context = context || window args = args ? args : [] const key = Symbol () context[key] = this const result = context[key](...args) delete context[key] return result }
2.实现apply apply和call的区别只有参数的区别,所以实现方式几乎一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Function .prototype.myapply = function (context,args ) { const func = this ; if (typeof this !== "function" ) { throw new TypeError ("not a function" ) } context = context || window args = args ? args : [] const key = Symbol () context[key] = this const result = context[key](...args) delete context[key] return result }
3.实现bind 以下是两种实现bind的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Function .prototype.bind = function (context,...args ) { const func = this if (typeof func !== 'function' ){ throw new TypeError ('Not A Function!' ) } args = args || [] return function newFn (...newArgs ) { if (this instanceof newFn){ return new func(...args, ...newArgs) } return func.apply(context,[...args,...newArgs]) } }
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 var ArrayPrototypeSlice = Array .prototype.slice;Function .prototype.bind = function (otherThis ) { if (typeof this !== 'function' ) { throw new TypeError ('Function.prototype.bind - what is trying to be bound is not callable' ); } var baseArgs= ArrayPrototypeSlice.call(arguments , 1 ), baseArgsLength = baseArgs.length, fToBind = this , fNOP = function ( ) {}, fBound = function ( ) { baseArgs.length = baseArgsLength; baseArgs.push.apply(baseArgs, arguments ); return fToBind.apply( fNOP.prototype.isPrototypeOf(this ) ? this : otherThis, baseArgs ); }; if (this .prototype) { fNOP.prototype = this .prototype; } fBound.prototype = new fNOP(); return fBound; };