this

一、使用this的原因(why)

对于前端开发者来说,this关键字是JavaScript中最复杂的机制之一,不同的位置使用,指向是不一样的,所以它到底有什么作用呢?为什么要花大量的时间去研究他呢

  • this提供了一种优雅的方式来隐式的“传递”一个对象的引用,可以方便我们简洁的设计API。特别是当结构越来越复杂时。
function GetObj (name) {
    this.name = name
}
GetObj.prototype.showName = function (){
alert(this.name)  // this指代GetObj这个对象
}
let p1 = new GetObj('张三')
p1.showName()

二、了解this(what----this到底是什么)

1、对this的误解
  • 认为this指向函数自身
function foo(num) {
   console.log('foo===', num)  // foo: 6 // foo: 7 // foo: 8 // foo: 9 
  // 记录 foo 被调用的次数 
  this.count++
}
foo.count = 0
for(var i = 0; i< 10; i++){
   if (i > 5) {         
      foo( i );    
   } 
}
 console.log( foo.count ); // 0 

this并没有指向自身的foo函数,而是指向了window。

  • 认为this的作用域指向函数的作用域
    在某种情况下它 是正确的,但是在其他情况下它却是错误的
function foo(){ 
    var a = 2; 
    this.bar(); 
} 
function bar() { 
    console.log(this); //window
    console.log(this.a); // undefined
} 
foo();

这个a作用域存在于foo函数中,所以在window这个作用域中找不到。但是由于var定义的a,在词法分析阶段会将a进行变量提升,所以window中会有一个a,但是没有值

2、与自然语言的对比

实际上js中的this和我们自然语言中的代词有类似性。比如英语中我们写"小明,你吃了么?"

注意上面的代词"小明",我们当然可以这样写:"小明,小明吃了么?" ,这种情况下我们没有使用this去重用代替小明。
主要和执行时候的上下文环境有关联

3、那this到底是什么呢?

this只跟函数的调用位置有关,是在函数被调用时发生绑定的;this的指向取决于函数在哪里被调用。

this指向的最终对象,跟调用位置以及应用的绑定规则有关

三、绑定规则(how--如何寻找函数的调用位置)

1、 默认绑定
  • 最常见的绑定规则,独立函数调用时默认绑定,也可以看做无法应用其他绑定规则时的默认规则
function foo() {      
    console.log( this.a ); // this指向window
} 
var a = 2; 
 
foo(); // 2
  • this指向
    • 在全局环境中,this 指向全局对象,在浏览器中,它就是 window 对象。
    • 普通函数是在全局环境中被调用:
      非严格模式下:指向全局对象window
      严格模式下:指向undefined(使用严格模式的时候,全局对象无法使用默认绑定)
function foo() {      
  "use strict";      
  console.log( this );  // undefined
  console.log( this.a );  // Uncaught TypeError: Cannot read property 'a' of undefined
} 
 "use strict";  
console.log( this ) // window
var a = 2; 
foo(); 
2、 隐式绑定
  • 当函数的调用存在上下文对象,或者说是否被某个对象拥有或者包含。this将会被绑定到这个上下文对象。
    this 始终会指向直接调用函数的上一级对象
function foo() {      
    console.log( this.a );   // 指向obj
} 
var obj = {      
    a: 2,     
    foo: foo  //--->foo: function () {      console.log( this.a );  } 
}; 
obj.foo(); // 2
  • 函数嵌套,每个function函数(非箭头函数)在每次调用时都会在函数内生成一个自己的this。
    当两个函数嵌套定义时,内层函数中的this与外层函数中的this是完全独立的。函数内this的值是在函数调用时才确定的,函数的调用方式不同,this也就不同
    1,当函数直接调用时 fn(); 函数内this的值是window对象,(在js严格模式"use strict";下,函数内this的值是null)
    2,当把这个函数赋值给一个对象的方法obj.abc = fn;
    调用obj.abc()时,函数内this的值是obj对象,也就是函数所在的对象。
var obj = {
    y: function() {
        console.log(this === obj);   // true
        console.log(this);   // Object {y: function}
        fn();  // 嵌套的函数不是对象的方法,直接调用,所以this指向window

        function fn() {
            console.log(this === obj);   // false
            console.log(this);   // Window 全局对象
        }
    }
}

obj.y();  
  • 问题:被隐式绑定的函数会丢失绑定对象,会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决于是否是严格模式。
    1、 函数引用传递给新的全局变量
function foo() {      
    console.log( this.a ); 
} 
var obj = {      
    a: 2,     
    foo: foo  
}; 
var bar = obj.foo; // bar是全局变量
var a = "oops, global"; // a 是全局对象的属性 
bar(); // "oops, global",bar() 其实是一个不带任何修饰的函数调用

2、将函数作为参数传入回调函数中

function foo() {      
    console.log( this.a ); 
} 
function doFoo(fn) {     // fn 其实引用的是 foo 
    fn(); // <-- 调用位置! 
} 
var obj = {     
    a: 2,     
    foo: foo  
}; 
var a = "oops, global"; // a 是全局对象的属性 
doFoo( obj.foo ); // "oops, global"
3、显式绑定
使用 call()和apply() 方法进行强制绑定
  • 他们的第一个参数都是指定函数运行时的this指向,第一个参数不传(参数为空)。或者为null、undefined。默认 this 指向全局对象(非严格模式)或 undefined(严格模式)。
var x = 1;

var obj = {
  x: 2
}

function fn() {
    console.log(this);
    console.log(this.x);
}

fn.call(obj)
// Object {x: 2}
// 2
fn.apply(obj)     
// Object {x: 2}
// 2

fn.call()         
// Window 全局对象
// 1

fn.apply(null)    
// Window 全局对象
// 1

fn.call(undefined)    
// Window 全局对象
// 1
  • 使用 call() 和 apply() 传入的this对象为原始值(字符串类型,布尔类型或者数字类型),这个原始值就会被转换成它的对象形式(new String()、new Bollean()、new Number())
  • 区别
    call()的第二个以及后续的参数是一个列表
    apply()的第二个参数是一个数组,所有的参数放在这个数组中
    参数列表和参数数组都将作为函数的参数进行执行
var x = 1
var obj = {
    x: 2
}

function sum (y, z) {
    console.log(this.x + y +z)
}
sum(3, 4) // 8
sum.call(obj, 3, 4) // 9
sum.apply(obj, [3, 4]) // 9
使用 bind() 方法进行强制绑定

调用 f.bind(someObject) 会创建一个与 f 具有相同函数体和作用域的函数,但是在这个新函数中,新函数的 this 会永久的指向 bind 传入的第一个参数,无论这个函数是如何被调用的。

var x = 1
var obj1 = {
    x: 2
}
var obj2 = {
    x: 3
}

function fn () {
    console.log(this)
    console.log(this.x)
}
var a = fn.bind(obj1)
var b = a.bind(obj2)

fn() // window 1

a() // {x: 2} 2

b() // {x: 2} 2 

a.call(obj2) // {x: 2} 2

在上面的例子中,虽然我们尝试给函数 a 重新指定 this 的指向,但是它依旧指向第一次 bind 传入的对象,即使是使用 call 或 apply 方法也不能改变这一事实,即永久的指向 bind 传入的第一次参数。

4、new绑定

使用 new 关键字,通过构造函数生成一个实例对象。此时,this 便指向这个新对象

var x = 1;

function Fn() {
   this.x = 2;
    console.log(this);  // Fn {x: 2}
}

var obj = new Fn();   // obj和Fn(..)调用中的this进行绑定
console.log(obj.x)   // 2

绑定规则优先级

如果某个调用位置可以应用多条规则该怎么办?为了 解决这个问题就必须给这些规则设定优先级。
毫无疑问,默认绑定的优先级别最低。
隐式绑定和显示绑定谁的优先级别更高呢?

function fn(){
    console.log(this.a)
}
let obj1 = {
    a: 1,
    fn: fn
}
let obj2 = {
    a: 2,
    fn: fn
}
obj1.fn()//1
obj2.fn()//2
obj1.fn.call(obj2)//2
obj2.fn.apply(obj1)//1
  • 可以看到,显式绑定优先级更高

隐式绑定和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
  • 可以看到 new 绑定比隐式绑定优先级高。

但是 new 绑定和显式绑定谁的优先级更高呢

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

bar 被硬绑定到 obj1 上,但是 new bar(3) 并没有像我们预计的那样把 obj1.a 修改为 3。相反,new 修改了硬绑定(到 obj1 的)调用 bar(..) 中的 this。因为使用了 new 绑定,我们得到了一个名字为 baz 的新对象,并且 baz.a 的值是 3。

当某个函数调用应用了这四种规则中的多条,那么优先级:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

5、箭头函数中this指向

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
箭头函数没有自己的this绑定。箭头函数中使用的this,其实是直接包含它的那个函数或函数表达式中的this

var a = 1;
let obj = {
    a: 2,
    fn1: () => {
        console.log('fn1',this.a) // 1 this指向window
        let fn3 = () => {
            console.log('fn3',this.a) // 1 this指向window
        }
        fn3()
        
        let fn4 = () => {
            console.log('fn4',this.a) // 1 this指向window
        }
        fn4.call(obj)
        // 由于箭头函数没有自己的this指针,通过 call() 或apply() 方法调用一个函数时,只能传递参数,他们的第一个参数会被忽略。
        fn4.apply(obj)
    },
    fn2: function() {
        console.log('fn2',this.a) // 2 this指向obj
    }
}
obj.fn1()
obj.fn2()

箭头函数的this看外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window。- 从父作用域继承this
同 bind 一样,箭头函数也很“顽固”,无法通过 call 和 apply 来改变 this 的指向,即传入的第一个参数被忽略。

图片转自掘金小册-前端面试之道

image.png

总结

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

  1. 由 new 调用?绑定到新创建的对象。
  2. 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
  3. 由上下文对象调用?绑定到那个上下文对象。
  4. 默认:在严格模式下绑定到 undefined,否则绑定到全局对象。
    一定要注意,有些调用可能在无意中使用默认绑定规则。如果想“更安全”地忽略 this 绑 定,你可以使用一个 DMZ 对象,比如 ø = Object.create(null),以保护全局对象。
    ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这 其实和 ES6 之前代码中的 self = this 机制一样。

https://www.cnblogs.com/kidsitcn/p/10985338.html
https://zhuanlan.zhihu.com/p/71490991
https://zhuanlan.zhihu.com/p/28536635
嵌套函数指向

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