this指向

89 阅读7分钟

默认绑定

默认绑定通常是指函数独立调用,不涉及其他绑定规则。非严格模式下,this指向window,严格模式下,this指向undefined

非严格模式

var foo = 123;
function print(){
	this.foo = 234;
    console.log(this); // window
	console.log(foo); // 234
}
print();	

严格模式

"use strict";
var foo = 123;
function print(){
    console.log('print this is ', this);  //undefined
    console.log(window.foo)
    console.log(this.foo);
}
console.log('global this is ', this);  //window
print();

let/const

let/const定义的变量存在暂时性死区,而且不会挂载到window对象上,因此print中是无法获取到a和b。

let a = 1;
const b = 2;
var c = 3;
function print() {
    console.log(this.a); //undefined
    console.log(this.b); //undefined
    console.log(this.c); //3
}
print();
console.log(this.a); //undefined

对象内执行

a = 1;
function foo() {
    console.log(this.a); 
}
const obj = {
    a: 10,
    bar() {
        foo(); // 1
    }
}
obj.bar(); 

函数内执行

var a = 1
function outer () {
  var a = 2
  function inner () { 
    console.log(this.a) // 1
  }
  inner()
}
outer()

自执行函数

a = 1;
(function(){
    console.log(this);  //window
    console.log(this.a) //1
}())
function bar() {
    b = 2;
    (function(){
        console.log(this);  //window
        console.log(this.b) //2
    }())
}
bar();

隐式绑定

函数的调用是在某个对象上触发的,即调用位置存在上下文对象,通俗点说就是**XXX.func()**这种调用模式。

此时functhis指向XXX,但如果存在链式调用,例如XXX.YYY.ZZZ.func,记住一个原则:this永远指向最后调用它的那个对象

隐式绑定

var a = 1;
function foo() {
    console.log(this.a); 
}
// 对象简写,等同于 {a:2, foo: foo}
var obj = {a: 2, foo}
foo(); //1
obj.foo(); //2

对象链式调用

var obj1 = {
    a: 1,
    obj2: {
        a: 2,
        foo(){
            console.log(this.a)
        }
    }
}
obj1.obj2.foo() // 2

取函数别名

a = 1
var obj = {
    a: 2,
    foo() {
        console.log(this.a)
    }
}
var foo = obj.foo;
obj.foo(); //2
foo(); //1

不要把这里理解成window.foo执行,如果foolet/const定义,foo不会挂载到window上,但不会影响最后的打印结果

var obj = { 
    a: 1, 
    foo() {
        console.log(this.a)
    } 
};
var a = 2;
var foo = obj.foo;
var obj2 = { a: 3, foo: obj.foo }

obj.foo(); //1
foo(); //2
obj2.foo(); /3

函数作为参数传递

function foo() {
  console.log(this,this.a) //window,2
}
function doFoo(fn) {
  console.log(this) //window
  fn()
}
var obj = { a: 1, foo }
var a = 2
doFoo(obj.foo)

obj.foo作为实参,在预编译时将其值赋值给形参fn,是将obj.foo指向的地址赋给了fn,此后fn执行不会与obj产生任何关系。fn为默认绑定。

function foo() {
  console.log(this,this.a) //window,2
}
function doFoo(fn) {
  console.log(this) //{a: 3, doFoo: ƒ}
  fn()
}
var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }
obj2.doFoo(obj.foo)
var length = 10
function fn () {
    console.log(this.length)
}
var obj = {
    length: 5,
    method (fn) {
        fn()                //10
        arguments[0]()      //2
    }
}
obj.method(fn, 1)

回调函数

var name='zcxiaobao';
function introduce(){
    console.log('Hello,My name is ', this.name);
}
const Tom = {
    name: 'TOM',
    introduce: function(){
        setTimeout(function(){
            console.log(this)
            console.log('Hello, My name is ',this.name);
        })
    }
}
const Mary = {
    name: 'Mary',
    introduce
}
const Lisa = {
    name: 'Lisa',
    introduce
}

Tom.introduce();
setTimeout(Mary.introduce, 100);
setTimeout(function(){
    Lisa.introduce();
},200);

Window {…}
Hello, My name is  zcxiaobao
Hello,My name is  zcxiaobao
Hello,My name is  Lisa

  • Tom.introduce()执行: console位于setTimeout的回调函数中,回调函数的this指向window
  • Mary.introduce直接作为setTimeout的函数参数,会发生隐式绑定丢失,this为默认绑定
  • Lisa.introduce执行虽然位于setTimeout的回调函数中,但保持xxx.fn模式,this为隐式绑定。
const Tom = {
    name: 'TOM',
    introduce: function(){
        _self = this
        setTimeout(function(){
            console.log('Hello, My name is ',_self.name);   // Hello, My name is  TOM
        })
    }
}
Tom.introduce()

隐式绑定丢失综合题

name = 'javascript' ;
let obj = {
    name: 'obj',
    A (){
        this.name += 'this';
        console.log(this.name)
    },
    B(f){
        this.name += 'this';
        f();
    },
    C(){
      setTimeout(function(){
          console.log(this.name);
      },1000);
    }
}
let a = obj.A;             
a();                        
obj.B(function(){           
    console.log(this.name); 
});                         
obj.C();                    
console.log(name);   

javascriptthis
javascriptthis
javascriptthis
javascriptthis

显式绑定

显式绑定比较好理解,就是通过call()、apply()、bind()等方法,强行改变this指向。

比较三种调用方式

function foo () {
  console.log(this.a)
}
var obj = { a: 1 }
var a = 2

foo() //2
foo.call(obj) //1
foo.apply(obj) //1
foo.bind(obj)
function foo () {
    console.log(this.a)
}
var obj = { a: 1 }
var a = 2

foo()
foo.call(obj)
foo().call(obj)

foo().call(obj): 对foo()执行的返回值执行callfoo返回值为undefined,执行call()会报错

function foo () {
    console.log(this.a)
    return function() {
        console.log(this.a)
    }
}
var obj = { a: 1 }
var a = 2

foo()
foo.call(obj)
foo().call(obj)

2
1
2
1

外层this与内层this

function foo () {
    console.log(this.a) //1
    return function() {
        console.log(this.a) //2
    }
}
var obj = { a: 1 }
var a = 2
foo.call(obj)()

对象中的call

var obj = {
    a: 'obj',
    foo: function () {
        console.log('foo:', this.a)
        return function () {
            console.log('inner:', this.a)
        }
    }
}
var a = 'window'
var obj2 = { a: 'obj2' }

obj.foo()()
obj.foo.call(obj2)()
obj.foo().call(obj2)
  • obj.foo()(): 第一层obj.foo()执行为隐式绑定,打印出foo:obj;第二层匿名函数为默认绑定,打印inner:window
  • obj.foo.call(obj2)(): 第一层obj.foo.call(obj2)使用call将obj.foo的this指向obj2,打印foo: obj2;第二层匿名函数默认绑定,打印inner:window
  • obj.foo().call(obj2): 第一层隐式绑定,打印:foo: obj,第二层匿名函数使用call将this指向obj2,打印inner: obj2

显式绑定扩展

apply求数组最值

JavaScript中没有给数组提供类似max和min函数,只提供了Math.max/min,用于求多个数的最值,所以可以借助apply方法,直接传递数组给Math.max/min

const arr = [1,10,11,33,4,52,17]
Math.max.apply(Math, arr)
Math.min.apply(Math, arr)

类数组转为数组

ES6未发布之前,没有Array.from方法可以将类数组转为数组,采用Array.prototype.slice.call(arguments)[].slice.call(arguments)将类数组转化为数组。

数组高阶函数

const obj = {a: 10}
const arr = [1, 2, 3, 4]
arr.forEach(function (val, key){
    console.log(`${key}: ${val} --- ${this.a}`)
}, obj)

0: 1 --- 10
1: 2 --- 10
2: 3 --- 10
3: 4 --- 10

new绑定

使用new来构建函数,会执行如下四部操作:

  1. 创建一个空的简单JavaScript对象(即{});
  2. 为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;
  3. 将步骤1新创建的对象作为this的上下文 ;
  4. 如果该函数没有返回对象,则返回this

属性加方法

function User (name, age) {
  this.name = name;
  this.age = age;
  this.introduce = function () {
    console.log(this.name)
  }
  this.howOld = function () {
    return function () {
      console.log(this.age)
    }
  }
}
var name = 'Tom';
var age = 18;
var zc = new User('zc', 24)
zc.introduce() //zc
zc.howOld()() //18

箭头函数

箭头函数没有自己的this,它的this指向外层作用域的this,且指向函数定义时的this而非执行时。

  • this指向外层作用域的this: 箭头函数没有this绑定,但它可以通过作用域链查到外层作用域的this
  • 指向函数定义时的this而非执行时: JavaScript是静态作用域,就是函数定义之后,作用域就定死了,跟它执行时的地方无关。

对象方法使用箭头函数

name = 'tom'
const obj = {
    name: 'zc',
    intro: () => {
        console.log('My name is ' + this.name)
    }
}
obj.intro() //My name is tom

箭头函数与普通函数比较

name = 'tom'
const obj = {
    name: 'zc',
    intro:function ()  {
        return () => {
            console.log('My name is ' + this.name)
        }
    },
    intro2:function ()  {
        return function() {
            console.log('My name is ' + this.name)
        }
    }
}
obj.intro2()()  //My name is tom
obj.intro()()  //My name is zc

箭头函数与普通函数的嵌套

name = 'window'
const obj1 = {
    name: 'obj1',
    intro:function ()  {
        console.log(this.name)
        return () => {
            console.log(this.name)
        }
    }
}
const obj2 = {
    name: 'obj2',
    intro: ()=>  {
        console.log(this.name)
        return function() {
            console.log(this.name)
        }
    }
}
const obj3 = {
    name: 'obj3',
    intro: ()=> {
        console.log(this.name)
        return () => {
            console.log(this.name)
        }
    }
}

obj1.intro()() //obj1  obj1
obj2.intro()() //window  window
obj3.intro()()  //window  window

new碰上箭头函数

function User(name, age) {
    this.name = name;
    this.age = age;
    this.intro = function(){
        console.log('My name is ' + this.name)
    },
    this.howOld = () => {
        console.log('My age is ' + this.age)
    }
}

var name = 'Tom', age = 18;
var zc = new User('zc', 24);
zc.intro();
zc.howOld();
  • zc是new User实例,因此构造函数User的this指向zc
  • zc.intro(): 打印My name is zc
  • zc.howOld(): howOld为箭头函数,箭头函数this由外层作用域决定,且指向函数定义时的this,外层作用域为User,this指向zc,打印My age is 24

call碰上箭头函数

箭头函数由于没有this,不能通过call\apply\bind来修改this指向,但可以通过修改外层作用域的this来达成间接修改

var name = 'window'
var obj1 = {
  name: 'obj1',
  intro: function () {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  },
  intro2: () => {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }
}
var obj2 = {
  name: 'obj2'
}
obj1.intro.call(obj2)()
obj1.intro().call(obj2)
obj1.intro2.call(obj2)()
obj1.intro2().call(obj2)

obj2
obj2
obj1
obj1
window
window
window
obj2

  • obj1.intro.call(obj2)(): 第一层函数为普通函数,通过call修改this为obj2,打印obj2。第二层函数为箭头函数,它的this与外层this相同,同样打印obj2。
  • obj1.intro().call(obj2): 第一层函数打印obj1,第二次函数为箭头函数,call无效,它的this与外层this相同,打印obj1
  • obj1.intro2.call(obj2)(): 第一层为箭头函数,call无效,外层作用域为window,打印window;第二次为普通匿名函数,默认绑定,打印window
  • obj1.intro2().call(obj2): 与上同,打印window;第二层为匿名函数,call修改this为obj2,打印obj2
  • 箭头函数没有this,它的this是通过作用域链查到外层作用域的this,且指向函数定义时的this而非执行时。
  • 不可以用作构造函数,不能使用new命令,否则会报错
  • 箭头函数没有arguments对象,如果要用,使用rest参数代替
  • 不可以使用yield命令,因此箭头函数不能用作Generator函数。
  • 不能用call/apply/bind修改this指向,但可以通过修改外层作用域的this来间接修改。
  • 箭头函数没有prototype属性。