一、this的绑定规则

默认绑定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1 独立函数被调用-this指向window
function foo() {
  console.log(this)
}
foo() 

// 2 函数定义在对象中,但是被独立调用-this指向window
var obj = {
  bar: function () {
	console.log(this)
  }
} 
var baz = obj.bar
baz()

// 3 高阶函数,函数作为实参被调用-this指向window
var obj2 = {
  bar: function () {
    console.log(this)
  }
}
function test(fn) {
  fn()
}
test(obj2.bar)

隐式绑定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function foo() {
  console.log(this)
}

var obj = {
  bar: foo
}

// 通过对象调用函数-this指向该对象
obj.bar()

显式绑定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function foo(name, age, height) {
  console.log(this, name, age, height)
}

// 1 apply调用:第一个参数绑定this,第二个参数传入额外的实参(以数组的形式)
foo.apply("apply", ["mocha", 18, 1.88])

// 2 call调用:第一个参数绑定this,其余传入参数列表作为实参
foo.call("call", "latte", 19, 1.99)

// 3 bind绑定
var obj = { name: "obj" }
// 调用函数时候,总是绑定到对象上,但函数不写入对象中
var bar = foo.bind(obj, "mocha", 18, 1.88)
bar() 

new 绑定

1
2
3
4
5
6
// new绑定-创建一个新的空对象,this指向这个对象
function foo() {
  console.log(this)
}

new foo()

注意

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

二、this的绑定优先级

  1. 默认绑定优先级最低
  2. 显式绑定高于隐式绑定
  3. new 绑定高于隐式绑定
  4. new 绑定高于bing,new不能和call,apply同时使用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function foo() {
  console.log(this)
}

// 1、显式绑定高于隐式绑定
var obj = { bar : foo }
obj.bar.apply("apply") // this指向apply
obj.bar.call("call")  // this指向call

var bar = foo.bind("bind")
var obj2 = { baz: bar }
obj2.baz() // this指向bind

// 2、new绑定高于隐式绑定
var obj3 = {
  foo: function () {
	console.log(this)
  }
}
new obj3.foo() // this指向新的空对象

// 3、new 绑定高于bind
var bindFn = foo.bind("bind")
new bindFn() // this指向新的空对象

三、绑定之外的情况

情况一

在显式绑定中,传入null/undefined,这个显式绑定会被忽略,使用默认规则,但在严格模式下将为原始值

1
2
3
4
5
6
function foo() {
  console.log(this)
}

foo.apply(null)  // this指向window,严格模式下为null
foo.apply(undefined) // this指向window,严格模式下为undefined

情况二

间接函数引用,使用默认规则

1
2
3
4
5
6
7
8
9
var obj = { 
  foo: function () {
	console.log(this)
  }
}

var obj2 = {}

(obj2.foo = obj.foo)() // this指向window

四、箭头函数的使用

特性

  • 箭头函数不会绑定this,也不会有arguments属性
  • 箭头函数不能作为构造函数

编写优化

  • 只有一个参数的时候可以省略()
  • 函数体只有一行代码时可以省略{},但会将这行代码作为返回值
  • 如果默认返回一个对象的时候,必须加上()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var names = [ "mocha", "latte", "coffee", "tea-milk" ]
names.forEach(item => console.log(item))

var arrFn = () => ({ name: "mocha", age: 18})

var nums = [ 1, 2, 3, 4, 5]
var totalNums = nums.filter(item => item % 2 === 0)
					.map(item => item ** 2)
					.reduce((preValue, item) => preValue + item)

箭头函数中的this用法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function request(url, callbackFn) {
  var result = ["aaa", "bbb", "ccc"]
  callbackFn(result)
}

var obj = {
  names: [],
  network: function () {
    // es5之前使用变量保存this
    // _this = this
    request("/names", (res) => {
	  this.names = [].concat(res)
    })
  }
}

obj.network()
console.log(obj.names)

五、this练习题分析

练习题一

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
var name = "window";

var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  },
};

function sayName() {
  var test = person.sayName;
  test(); // window 默认绑定(独立函数调用)
  person.sayName(); // person 隐式绑定
  person.sayName(); // person 隐式绑定
  (b = person.sayName)(); // window 间接函数调用(独立函数调用)
}

sayName();

练习题二

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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(); // window (独立函数调用)
person2.foo1.call(person2); // person2 (显示绑定)

person1.foo2(); // window (箭头函数不绑定this,上层作用域是全局)
person1.foo2.call(person2); // window (箭头函数不适用this的绑定规则)

person1.foo3()(); // window (独立函数调用)
person1.foo3.call(person2)(); // window (独立函数调用)
person1.foo3().call(person2); // person2 (最终调用返回函数时,使用的是显示绑定)

person1.foo4()(); // person1 (箭头函数不绑定this,上层作用域是person1)
person1.foo4.call(person2); // person2 (上层作用域被显示地绑定为person2)
person1.foo4().call(person2); // peerson1 (上层找到了person1)

练习题三

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 (隐式绑定)
person1.foo1.call(person2); // person2 (显示绑定)

person1.foo2(); // person1 上层作用域是person1
person1.foo2.call(person2); // person1 call不绑定箭头函数中的this

person1.foo3()(); // window (独立函数调用)
person1.foo3.call(person2)(); // window (独立函数调用)
person1.foo3().call(person2); // person2 (显示绑定)

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

练习题四

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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()(); // window (独立函数调用)
person1.obj.foo1.call(person2)(); // window (独立函数调用)
person1.obj.foo1().call(person2); // person2 (显示绑定)

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