this怎么那么难呢?(接上篇-1)

2019-12-31 11:25:00 浏览数 (1)

如果没有能力做选择,那就只能被选择~


2019年12月22日,星期六。距离新年还有9天了,宝宝们可以提前想想2020年的目标了。也可以把目标留言在文章下方,目标说给别人听能起到督促自己的作用,不信你就试试!

接上篇this的绑定方式,第一种是默认绑定,也就是独立函数的调用,这种情况就看作是无法应用其他规则的默认绑定。

第二种是隐式绑定,我们接着看:

隐式绑定


调用位置是否有上下文,或者说被某个对象拥有或者包含。思考下面代码:

代码语言:javascript复制
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是一样的。

对象引用链中只有最顶层或者最后一层会影响调用位置。

代码语言:javascript复制
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。

隐式丢失

代码语言:javascript复制
function foo(){
  console.log(this.a)
}
var obj = {
  a :2,
  foo: foo
}
var bar = obj.foo;  // 给将函数foo赋值给bar
var a = "000000"
bar(); // "000000" 这里才是foo真正调用的地方。是默认绑定
代码语言:javascript复制
function foo(){
  console.log(this.a);
}
function doFoo(fn){
  fn()   // 被调用的位置
}
var obj ={
  a :2,
  foo: foo
}
var a = "123"
doFoo(obj.foo);   // "123"

把函数传入语言内置的函数而不是传入自己声明的函数,结果是一样的:

代码语言:javascript复制
function foo(){
  console.log(this.a)
}
var obj = {
  a:2,
  foo: foo
}
var a = "hello"
setTimeout(obj.foo, 100);  // "hello"

js环境内置的setTimeout()函数实际和下面的伪代码类似:

代码语言:javascript复制
function setTimeout(fn,delay){
  // 等待delay毫秒  
  fn(); // 真正调用位置
}

显式绑定


使用call和apply;他们的第一个参数是一个对象,他们会把这个对象绑定到this,接着调用函数时指定这个this。

代码语言:javascript复制
function foo(){
  console.log(this.a)
}
var obj = {
  a: 2
}
foo.call(obj); //2

如果传入的第一个参数是原始值(string、boolean、number)来当做this的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(...)、new Boolean()、new Number())。这通常被称作“装箱”。

代码语言:javascript复制
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:创建一个包裹函数,传入所有的参数并返回接收到的所有值:

代码语言:javascript复制
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可以重复使用的辅助函数:

代码语言:javascript复制
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,用法如下:

代码语言:javascript复制
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表达式中的函数调用会自动返回这个新对象

优先级


默认绑定的优先级是四条规则中最低的。

我们来看看隐式绑定和显示绑定哪个优先级更高:

代码语言:javascript复制
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和隐式绑定的优先级:

代码语言:javascript复制
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进行测试:

代码语言:javascript复制
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

0 人点赞