笔记-this绑定

目录

  • 1、核心
  • 2、绑定规则
  • 3、绑定例外
  • 4、优先级
  • 5、箭头函数
  • 6、总结
  • 7、面试题
  • 8、参考书籍、视频

很久没更新文章了,起因是源于gpt,这东西太强大了,对博客造成了很大的冲击。后来想想,有些东西总结下来是对自己的沉淀,遂重新提起笔来...

2021年的时候,刚开始写ReactNative,当时最苦恼就是this的绑定的逻辑。对于this我最初的理解就是它本身,和之前OC中的self大概是一样的吧。开始的时候遇到个例,发现都乱了,可能是A的方法调用到B的方法上了,B调用刷新可能刷到C上了...

后来我发现了一个很省事的方法,直接用ES6箭头函数就没事儿了,或者在初始化的时候直接用bind方法先绑定上也不会出现啥问题,用了大半年,不亦乐乎。

直到我发现一本用了大篇幅讲 this 的书 《你不知道的JavaScript》,又搜到一个叫codewhy的前端讲师的 高级JS视频,才算彻底把这个搞明白了。

书上有一段话我觉得写的特别好,也送给正在读博客的你

遇到这样的问题时,许多开发者并不会深入思考为什么 this 的行为和预期的不一致,也不会试图回答那些很难解决但却非常重要的问题。他们只会回避这个问题并使用其他方法来达到目的。

从某种角度来说这个方法确实“解决”了问题,但可惜它忽略了真正的问题——无法理解 this 的含义和工作原理——而是返回舒适区。


一、核心

1、函数在调用时,JavaScript会默认给this绑定一个值;
2、this的绑定和定义的位置(编写的位置)没有关系;
3、this的绑定和调用方式以及调用的位置有关系;
4、this是在运行时被绑定的;


二、绑定规则

  • 2.1、默认绑定
  • 函数直接调用(不绑定任何对象或采用对象引用方式调用)
  • this指向: 严格模式-> window;非严格模式-> undefined
- 2.1.1、简单函数调用
function foo() {
  console.log(this); // window / undefined
}

foo();



- 2.1.2、函数调用链
function foo1() {
    console.log(this); // window / undefined
    foo2();
}

function foo2() {
    console.log(this); // window / undefined
    foo3();
}

function foo3() {
    console.log(this); // window / undefined
}

foo();



- 2.1.3、将函数作为参数,传入到另一个函数中
function foo(func) {
  func();
}

function foo1() {
  console.log(this); // window / undefined
}

foo(foo1);
  • 2.2、隐式绑定
  • 通过某个对象发起的函数调用
  • 隐式绑定丢失:应用默认绑定
- 2.2.1、通过对象调用函数
function foo() {
    console.log(this); // obj对象
}

var obj = {
    name:"qiangzi",
    foo: foo
}

obj.foo();



- 2.2.2、对象属性引用链
function foo() {
    console.log(this); // obj1对象
}

var obj1 = {
    name:"qiangzi-1",
    foo: foo
}

var obj2 = {
    name:"qiangzi-1",
    obj1: obj1
}

obj2.obj1.foo();



- 2.2.3、隐式绑定丢失(应用默认绑定)
function foo() {
    console.log(this);  // window / undefined
}

var obj1 = {
    name: "qinagzi-1",
    foo: foo
}

// 将obj1的foo赋值给foo2
var foo2 = obj1.foo;
foo2();
  • 2.3、显示绑定
  • 在分析 '隐式绑定' 时,必须在一个对象内部包含一个只想函数的属性,通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象中。

  • 如果不想在对象内部包含函数引用,同是又希望对象上强制调用函数,该如何做呢?

  • 可以使用函数的 call(..) 和 apply(..) 方法,在调用函数同时,会将this绑定到这个传入的对象上。

  • 直接指定 this 的绑定对象,称为 显示绑定

  • call(..) 和 apply(..) ,解决之前 2.2.3 绑定丢失问题,可以使用 bind 硬绑定

- 2.3.1、call、apply
function foo() {
    console.log(this);
}

var obj1 = {
    name:"qiangzi-1",
    foo: foo
}

var obj2 = {
    name:"qiangzi-2",
    foo: foo
}
foo.call(obj1);   // obj1对象
foo.apply(obj2);  // obj2对象

* 从this绑定的角度来说,call和apply函数效果是一样的,区别体现在其他参数上。



- 2.3.2、硬绑定:显示绑定变种(解决绑定丢失问题),创建一个包裹函数,内部手动调用显示绑定
function foo() {
    console.log(this);
}

var obj = {
    name: "qiangzi"
}

var bar = function() {
    foo.call(obj);
}

bar(); // obj
setTimeout(bar, 100); // obj

// 硬绑定的bar不可能在修改它的this
bar.call(window); // obj



- 2.3.3、bind函数(包裹函数的应用)
function foo(something) { 
    console.log( this.a, something);
    return this.a + something; 
}

var obj = { a:2 };
var bar = function() {
    return foo.apply( obj, arguments );
};
var b = bar( 3 ); // 2 3 
console.log( b ); // 5
  • 2.4、new绑定

使用 new 来调用函数,会自动执行下面的操作

  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行 [[ 原型 ]] 连接。
  3. 这个新对象会绑定到函数调用的 this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
// 创建Person
function Person(name) {
  console.log(this); // Person {}
  this.name = name; // Person {name: "qiangzi"}
}

var p = new Person("qiangzi");
console.log(p);

三、绑定例外

  • 3.1、忽略显示绑定

在显示绑定中,传入一个null或undefined,这个显示绑定会被忽略,使用默认规则

function foo() {
  console.log(this);
}

var obj = {
  name: "qiangzi-01"
}

foo.call(obj); // obj对象
foo.call(null); // window
foo.call(undefined); // window

var bar = foo.bind(null);
bar(); // window
  • 3.2、间接函数引用

创建一个函数的 间接引用,这种情况使用默认绑定规则。

- 3.2.1、值赋值结果:(num2 = num1)的结果是num1的值;

var num1 = 100;
var num2 = 0;
var result = (num2 = num1);
console.log(result); // 100



- 3.2.2、函数赋值结果:赋值(obj2.foo = obj1.foo)的结果是foo函数;函数直接调用,默认绑定。

function foo() {
  console.log(this);
}

var obj1 = {
  name: "obj1",
  foo: foo
}; 

var obj2 = {
  name: "obj2"
}

obj1.foo(); // obj1对象
(obj2.foo = obj1.foo)();  // window

四、优先级

new绑定 > 显示绑定(bind)> 隐式绑定 > 默认绑定

new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高

4.1、默认绑定的优先级是四条规则中最低的
- 因为存在其他规则时,就会通过其他规则的方式来绑定this;


4.2、显示绑定 > 隐示绑定
function foo() {
    console.log(this);
}

var obj1 = {
    name: "obj1",
    foo: foo
}

var obj2 = {
    name: "obj2",
    foo: foo
}

// 隐式绑定
obj1.foo();  // obj1
obj2.foo();  // obj2

// 隐式绑定和显示绑定同时存在(根据打印结果,说明显式绑定优先级更高)
obj1.foo.call(obj2); // obj2, 
obj2.foo.call(obj1); // obj1, 



4.3、new绑定 > 隐式绑定
function foo() {
  console.log(this);
}

var obj = {
  name: "why",
  foo: foo
}

new obj.foo(); // foo对象, 说明new绑定优先级更高

五、箭头函数

箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。

思考:为什么在setTimeout的回调函数中可以直接使用this呢?

- 5.1、箭头函数
function foo() {
    return () => {
        console.log(this.a);
    };
}

let obj1 = {
    a: 2
};

let obj2 = {
    a: 22
};

let bar = foo.call(obj1); // foo this指向obj1
bar.call(obj2); // 输出2🌟🌟🌟🌟  这里执行箭头函数 并试图绑定this指向到obj2



- 5.2、箭头函数-setTimeout 1
var obj = {
    data: [],
    getData: function() {
        setTimeout(() => { // 箭头函数向上层作用域查找, getData在调用时被绑定到了obj
            var res = ["abc", "cba", "nba"];
            this.data.push(...res);
        }, 1000);
    }
}

obj.getData();


- 5.2、箭头函数-setTimeout 2
var obj = {
    data: [],
    getData: () => {
        setTimeout(() => {
        console.log(this); // 箭头函数向上层作用域查找, getData还是箭头函数,再向上找到全局 window
      }, 1000);
    }
}

obj.getData();

六、总结

判断一个运行中函数的 this 绑定,直接找到这个函数的调用位置,顺序应用下面这四条规则来判断 this 的绑定对象。

1、由 new 调用:绑定到新创建的对象。 
2、由 call 或者 apply(或者 bind)调用:绑定到指定的对象。
3、由上下文对象调用:绑定到那个上下文对象。 
4、默认:在严格模式下绑定到 undefined,否则绑定到全局对象。


- 上下文对象调用
function foo(el) { 
    console.log( el, this.id ); 
}

var obj = { id: "awesome" };

// 调用 foo(..) 时把 this 绑定到 obj [1, 2, 3].forEach( foo, obj );
// 1 awesome 2 awesome 3 awesome

七、面试题

这些面试题来源于 CodeWhy 的公众号,虽然有些故意为之,但是也不失为检测对this绑定的理解程度,盖上答案自己去检测一下看看吧。

  • 7.1、面试题一
var name = "window";
var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  }
};
function sayName() {
  var sss = person.sayName;
  sss(); 
  person.sayName(); 
  (person.sayName)(); 
  (b = person.sayName)(); 
}
sayName();



分析
function sayName() {
  var sss = person.sayName;
  // 独立函数调用,没有和任何对象关联
  sss(); // window
  // 关联
  person.sayName(); // person
  (person.sayName)(); // person
  (b = person.sayName)(); // window
}
  • 7.2、面试题二
var name = 'window'
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

person1.foo1(); 
person1.foo1.call(person2); 

person1.foo2();
person1.foo2.call(person2);

person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);

person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);



分析
// 隐式绑定,肯定是person1
person1.foo1(); // person1
// 隐式绑定和显示绑定的结合,显示绑定生效,所以是person2
person1.foo1.call(person2); // person2

// foo2()是一个箭头函数,不适用所有的规则
person1.foo2() // window
// foo2依然是箭头函数,不适用于显示绑定的规则
person1.foo2.call(person2) // window

// 获取到foo3,但是调用位置是全局作用于下,所以是默认绑定window
person1.foo3()() // window
// foo3显示绑定到person2中
// 但是拿到的返回函数依然是在全局下调用,所以依然是window
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数,通过显示绑定到person2中,所以是person2
person1.foo3().call(person2) // person2

// foo4()的函数返回的是一个箭头函数
// 箭头函数的执行找上层作用域,是person1
person1.foo4()() // person1
// foo4()显示绑定到person2中,并且返回一个箭头函数
// 箭头函数找上层作用域,是person2
person1.foo4.call(person2)() // person2
// foo4返回的是箭头函数,箭头函数只看上层作用域
person1.foo4().call(person2) // person1
  • 7.3、面试题三
var name = 'window'
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  },
  this.foo2 = () => console.log(this.name),
  this.foo3 = function () {
    return function () {
      console.log(this.name)
    }
  },
  this.foo4 = function () {
    return () => {
      console.log(this.name)
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1()
person1.foo1.call(person2)

person1.foo2()
person1.foo2.call(person2)

person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)

person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)




分析
// 隐式绑定
person1.foo1() // peron1
// 显示绑定优先级大于隐式绑定
person1.foo1.call(person2) // person2

// foo是一个箭头函数,会找上层作用域中的this,那么就是person1
person1.foo2() // person1
// foo是一个箭头函数,使用call调用不会影响this的绑定,和上面一样向上层查找
person1.foo2.call(person2) // person1

// 调用位置是全局直接调用,所以依然是window(默认绑定)
person1.foo3()() // window
// 最终还是拿到了foo3返回的函数,在全局直接调用(默认绑定)
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数后,通过call绑定到person2中进行了调用
person1.foo3().call(person2) // person2

// foo4返回了箭头函数,和自身绑定没有关系,上层找到person1
person1.foo4()() // person1
// foo4调用时绑定了person2,返回的函数是箭头函数,调用时,找到了上层绑定的person2
person1.foo4.call(person2)() // person2
// foo4调用返回的箭头函数,和call调用没有关系,找到上层的person1
person1.foo4().call(person2) // person1
  • 7.4、面试题四
var name = 'window'
function Person (name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {
        console.log(this.name)
      }
    },
    foo2: function () {
      return () => {
        console.log(this.name)
      }
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()()
person1.obj.foo1.call(person2)()
person1.obj.foo1().call(person2)

person1.obj.foo2()()
person1.obj.foo2.call(person2)()
person1.obj.foo2().call(person2)



分析
// obj.foo1()返回一个函数
// 这个函数在全局作用于下直接执行(默认绑定)
person1.obj.foo1()() // window
// 最终还是拿到一个返回的函数(虽然多了一步call的绑定)
// 这个函数在全局作用于下直接执行(默认绑定)
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2

// 拿到foo2()的返回值,是一个箭头函数
// 箭头函数在执行时找上层作用域下的this,就是obj
person1.obj.foo2()() // obj
// foo2()的返回值,依然是箭头函数,但是在执行foo2时绑定了person2
// 箭头函数在执行时找上层作用域下的this,找到的是person2
person1.obj.foo2.call(person2)() // person2
// foo2()的返回值,依然是箭头函数
// 箭头函数通过call调用是不会绑定this,所以找上层作用域下的this是obj
person1.obj.foo2().call(person2) // obj

八、参考:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容