在 JavaScript 执行上下文创建阶段,会进行 this 绑定,当时提到有五种绑定规则:
默认绑定
隐式绑定
显式绑定(硬绑定)
new 绑定
箭头函数绑定
一、默认绑定 在全局环境中默认 this 指向全局对象。严格模式下,this 会指向 undefined 。
二、隐式绑定 当函数引用有上下文对象时,隐式绑定规则会把函数中 this 绑定到这个上下文对象。
1 2 3 4 5 6 7 8 9 10 function foo ( ) { console .log ( this .a ); } var obj = { a : 2 , foo : foo }; obj.foo ();
2.1 隐式缺失 被隐式绑定的函数特定情况下会丢失绑定对象,应用默认绑定,把this绑定到全局对象或者undefined上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function foo ( ) { console .log ( this .a ); } var obj = { a : 2 , foo : foo }; var bar = obj.foo ; var a = "oops, global" ; bar ();
参数传递就是一种隐式赋值,传入函数时也会被隐式赋值。回调函数丢失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 function foo ( ) { console .log ( this .a ); } function doFoo (fn ) { fn (); } var obj = { a : 2 , foo : foo }; var a = "oops, global" ; doFoo ( obj.foo ); function setTimeout (fn, delay ) { fn (); }
三、显示绑定 通过call(..) 或者 apply(..)方法。第一个参数是一个对象,在调用函数时将这个对象绑定到this。因为直接指定this的绑定对象,称之为显示绑定。
1 2 3 4 5 6 7 8 9 function foo ( ) { console .log ( this .a ); } var obj = { a : 2 }; foo.call ( obj );
显示绑定无法解决丢失绑定问题。
3.1 call 和 apply 区别 call 和 apply 的作用是一样的,只是参数的形式不同。
apply 方法传入两个参数:一个是函数上下文对象,另一个是作为函数参数的所组成的数组 。
calll 方法第一个参数也是函数上下文对象,后面的是参数列表,而不是单个数组。
apply 适用于参数本来就是数组的有关联的,而 call 适用参数散列的。两个作用相同,哪个方便用哪个即可。
四、绑定丢失的解决方案 4.1 硬绑定 创建函数bar(),并在它的内部手动调用foo.call(obj),强制把foo的this绑定到了obj。这种方式让我想起了借用构造函数继承,没看过的可以点击查看 JavaScript常用八种继承方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function foo ( ) { console .log ( this .a ); } var obj = { a : 2 }; var bar = function ( ) { foo.call ( obj ); }; bar (); setTimeout ( bar, 100 ); bar.call ( window );
ES5 内置了Function.prototype.bind,bind会返回一个硬绑定的新函数,用法如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 function foo (something ) { console .log ( this .a , something ); return this .a + something; } var obj = { a : 2 }; var bar = foo.bind ( obj );var b = bar ( 3 ); console .log ( b );
4.2 bind 和 call 的区别 ? ES5中添加了 bind 方法,低版本IE不兼容,与 bind 功能类似,接收两部分参数,第一个参数函数上下文对象,后面是参数列表。
不同点:
1 2 3 4 5 6 7 8 9 10 11 function foo () { console .log (this .a ) } var obj = { a : 10 } var bar = foo.bind (obj)bar ()
call 是把第二个及以后的参数作为 func 方法的实参传进去,而 func1 方法的实参实则是在 bind 中参数的基础上再往后排。
CodePen
兼容低版本 IE 浏览器
1 2 3 4 5 6 7 8 9 10 if (!Function .prototype .bind ) { Function .prototype .bind = function ( ) { var self = this , context = [].shift .call (arguments ), args = [].slice .call (arguments ); return function ( ) { self.apply (context,[].concat .call (args, [].slice .call (arguments ))); } } }
4.3 API调用的 ‘上下文’ JS许多内置函数提供了一个可选参数,被称之为“上下文”(context),其作用和bind(..)一样,确保回调函数使用指定的this。这些函数实际上通过call(..)和apply(..)实现了显式绑定。
1 2 3 4 5 6 7 8 9 10 11 12 function foo (el ) { console .log ( el, this .id ); } var obj = { id : "awesome" } var myArray = [1 , 2 , 3 ]myArray.forEach ( foo, obj );
五、new 绑定 构造函数其实只是一个普通的函数,不同点是使用 new 调用,称为构造调用。
new 调用函数时发生什么?
创建一个新对象。
这个新对象连接到原型,this 指向这个新对象。
执行构造函数中的代码,绑定其他属性。
返回新对象。
1 2 3 4 5 6 7 8 9 function create ( ) { var obj = new Object () Con = [].shift .call (arguments ) obj.__proto__ = Con .prototype var ret = Con .apply (obj, arguments ) return ret instanceof Object ? ret : obj }
用new Object()的方式新建了一个对象obj
取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 arguments会被去除第一个参数
将 obj的原型指向构造函数,这样obj就可以访问到构造函数原型中的属性
使用apply,改变构造函数this 的指向到新建的对象,这样 obj就可以访问到构造函数中的属性
返回 obj
把 null 或者 undefined 作为 this 绑定对象传入 call 、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定。
六、箭头函数 其实大部分情况下可以用一句话来概括,this总是指向调用该函数的对象。
但是对于箭头函数并不是这样,是根据外层(函数或者全局)作用域(词法作用域)来决定this。
对于箭头函数的this总结如下:
箭头函数不绑定this,箭头函数中的this相当于普通变量。
箭头函数的this寻值行为与普通变量相同,在作用域中逐级寻找。
箭头函数的this无法通过bind,call,apply来直接修改(可以间接修改)。
改变作用域中this的指向可以改变箭头函数的this。
eg. function closure(){()=>{//code }},在此例中,我们通过改变封包环境closure.bind(another)(),来改变箭头函数this的指向。
测试题目
七、call 和 apply 的应用 7.1 合并数组 1 2 3 4 5 6 var a = [1 ,2 ,3 ]var b = [4 ,5 ,6 ]Array .prototype .push .apply (a, b)console .log (a)
注意:第二个数组的值不能太大,不同的引擎有不同的限制,JS核心限制为65535,超过限制会抛出异常或者丢失多余的参数。 解决方法:将参数数组切块后循环传入方法。
1 2 3 4 5 6 7 8 9 10 function concatOfArray (arr1, arr2 ) { var QUANTUM = 32768 ; for (var i = 0 , len = arr2.length ; i < len; i += QUANTUM ) { Array .prototype .push .apply ( arr1, arr2.slice (i, Math .min (i + QUANTUM , len) ) ); } return arr1; }
7.2 获取数组中最大值和最小值 1 2 3 4 var numbers = [1 ,30 ,10 ,423 ,-123 ]Math .max .apply (Math , numbers) Math .max .call (Math , ...numbers)
7.3 验证是否为数组 1 2 3 4 function isArray (obj ) { return Object .prototype .toString .call (obj) === '[object Array]' } isArray ([1 ,2 ,3 ])
使用 Object.prototype.toString() 来检测。
7.4 类数组转数组 类数组特性:
具有指向对象元素的数字索引下标和 length 属性。
不具有 push/shift/forEach/indexOf 等数组方法。
类数组是一个对象,JS中的一种数据结构。比如 arguments 对象,DOM API 返回的节点列表。可以通过 [].slice.call 转换成真正的数组。
1 2 3 4 5 var arr = [].slice .call (arguments );let arr = Array .from (arguments );let arr = [...arguments ];
八、测试题 CodePen - 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 30 31 32 function Person (name) { this .name = name; this .show1 = function ( ) { console .log (this .name ) } this .show2 = () => console .log (this .name ) this .show3 = function ( ) { return function ( ) { console .log (this .name ) } } this .show4 = function ( ) { return () => console .log (this .name ) } } var personA = new Person ('personA' )var personB = new Person ('personB' )personA.show1 () personA.show1 .call (personB) personA.show2 () personA.show2 .call (personB) personA.show3 ()() personA.show3 ().call (personB) personA.show3 .call (personB)() personA.show4 ()() personA.show4 ().call (personB) personA.show4 .call (personB)()
第二题:
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 31 32 33 34 35 function Person (name) { this .name = name; this .show1 = function ( ) { console .log (this .name ) } this .show2 = () => console .log (this .name ) this .show3 = function ( ) { return function ( ) { console .log (this .name ) } } this .show4 = function ( ) { return () => console .log (this .name ) } } var personA = new Person ('personA' )var personB = new Person ('personB' )personA.show1 () personA.show1 .call (personB) personA.show2 () personA.show2 .call (personB) personA.show3 ()() personA.show3 ().call (personB) personA.show3 .call (personB)() personA.show4 ()() personA.show4 ().call (personB) personA.show4 .call (personB)()
参考 木易杨前端进阶