this怎么那么难呢?(接上篇-1)
- 2019 年 12 月 31 日
- 筆記
如果没有能力做选择,那就只能被选择~
2019年12月22日,星期六。距离新年还有9天了,宝宝们可以提前想想2020年的目标了。也可以把目标留言在文章下方,目标说给别人听能起到督促自己的作用,不信你就试试!
接上篇this的绑定方式,第一种是默认绑定,也就是独立函数的调用,这种情况就看作是无法应用其他规则的默认绑定。
第二种是隐式绑定,我们接着看:
隐式绑定
调用位置是否有上下文,或者说被某个对象拥有或者包含。思考下面代码:
function foo(){ console.log(this.a); } var obj = { a :2, foo: foo } obj.foo(); // 2
以上,首先需要注意的是foo()的声明方式,然后是如何被当做引用属性添加到obj中的。但是无论是直接在obj中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。
然而,调用位置会使用obj上线文来引用函数,因此可以说函数调用时obj对象“拥有”或“包含”它。
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因调用foo()时this被绑定到obj,因此this.a和obj.a是一样的。
对象引用链中只有最顶层或者最后一层会影响调用位置。
function foo(){ console.log(this.a) } var obj2={ a:42, foo: foo } var obj1={ a: 2, obj2: obj2 } obj1.obj2.foo() // 42
在我看来foo被调用的位置 实际上还是obj2中,所以this指向了obj2。
隐式丢失
function foo(){ console.log(this.a) } var obj = { a :2, foo: foo } var bar = obj.foo; // 给将函数foo赋值给bar var a = "000000" bar(); // "000000" 这里才是foo真正调用的地方。是默认绑定
function foo(){ console.log(this.a); } function doFoo(fn){ fn() // 被调用的位置 } var obj ={ a :2, foo: foo } var a = "123" doFoo(obj.foo); // "123"
把函数传入语言内置的函数而不是传入自己声明的函数,结果是一样的:
function foo(){ console.log(this.a) } var obj = { a:2, foo: foo } var a = "hello" setTimeout(obj.foo, 100); // "hello"
js环境内置的setTimeout()函数实际和下面的伪代码类似:
function setTimeout(fn,delay){ // 等待delay毫秒 fn(); // 真正调用位置 }
显式绑定
使用call和apply;他们的第一个参数是一个对象,他们会把这个对象绑定到this,接着调用函数时指定这个this。
function foo(){ console.log(this.a) } var obj = { a: 2 } foo.call(obj); //2
如果传入的第一个参数是原始值(string、boolean、number)来当做this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(…)、new Boolean()、new Number())。这通常被称作“装箱”。
function foo(){ console.log(this.a); } car obj = { a : 2 } var bar = function (){ foo.call(obj) } bar(); // 2 setTimeout(bar,100) // 2 bar.call(window); // 2 硬绑定的bar不可能再修改它的this
以上,创建了函数bar,并在他的内部手动调用了doo.call(obj),因此强制把foo的shis绑定到了obj。无论之后如何调用函数bar,它总会手动在obj上调用foo。这种绑定是一种显示的强制绑定,即硬绑定。
硬绑定用法1:创建一个包裹函数,传入所有的参数并返回接收到的所有值:
function foo(something){ console.log(this.a,something); return this.a + something; } var obj = { a :2 } var bar = functiong(){ return foo.apply(obj, arguments); } var b = bar(3) // 2 3 console.log(b) // 5
硬绑定用法2:创建一个i可以重复使用的辅助函数:
function foo(something){ console.log(this.a, something); return this.a + something } functiong bind(fn, obj){ return function (){ return fn.apply(obj , arguments); } } var obj = { a : 2 } var bar = bind( foo, obj) var b = bar(3); // 2 3 console.log(b) // 5
硬绑定是非常常用的模式,所以ES5中提供了内置的方法Function.prototype.bind,用法如下:
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); // 2 3 console.log(b) // 5
bind(…)会返回一个硬编码的新函数,他会把参数设置为this的上下并调用原始函数。
new绑定
这是第四条也是最后一条this的绑定规则。
在JS中,构造函数只是一些使用new操作符时被调用的函数。他们并不会属于某个类,也不会实例化一个类。实际上,他们甚至都不会说是一种特殊的函数类型,他们只是被new操作符调用的普通函数而已。
实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
使用new调用函数时,具体都做了什么:
- 创建一个全新的对象
- 这个新对象会被执行【原型】连接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
优先级
默认绑定的优先级是四条规则中最低的。
我们来看看隐式绑定和显示绑定哪个优先级更高:
function foo() { console.log( this.a ); } var obj1 = { a: 2, foo: foo }; var obj2 = { a: 3, foo: foo }; obj1.foo(); // 2 obj2.foo(); // 3 obj1.foo.call( obj2 ); // 3 obj2.foo.call( obj1 ); // 2
so , 显示绑定的优先级高于隐式绑定
看看new和隐式绑定的优先级:
function foo(something) { this.a = something; } var obj1 = { foo: foo }; var obj2 = {}; obj1.foo( 2 ); console.log( obj1.a ); // 2 obj1.foo.call( obj2, 3 ); console.log( obj2.a ); // 3 var bar = new obj1.foo( 4 ); console.log( obj1.a ); // 2 console.log( bar.a ); // 4
so, new绑定的优先级高于隐式绑定。但是new 和显示绑定的优先级谁更高呢?
new和call/apply无法一起使用,因此无法进行直接测试。我们用bind进行测试:
function foo(something) { this.a = something; } var obj1 = {}; var bar = foo.bind( obj1 ); bar( 2 ); console.log( obj1.a ); // 2 var baz = new bar(3); console.log( obj1.a ); // 2 console.log( baz.a ); // 3
以上,new 修改了硬绑定调用bar中的this.我们得到了一个新对象,并且baz.a的值是3.
也就是说优先级为:new > call/apply > 隐式绑定> 默认绑定
愿我们有能力不向生活缴械投降—Lin