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调用函数时,具体都做了什么:

  1. 创建一个全新的对象
  2. 这个新对象会被执行【原型】连接
  3. 这个新对象会绑定到函数调用的this
  4. 如果函数没有返回其他对象,那么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