This指向

  • 函数在调用时,JavaScript会默认给this绑定一个值;
  • this 的指向不是在创建时就绑定了,而是在调用时被绑定的;
  • this的绑定和定义的位置(编写的位置)没有关系;
  • this的绑定和调用方式以及调用的位置有关系;

四种绑定规则

  1. 默认绑定;
  2. 隐式绑定;
  3. 显示绑定;
  4. new绑定;

默认绑定(独立调用)

独立的调用,this 就代表 window 对象。

独立调用:xxx()为调用,没有小括号的不叫调用;函数前面没有别的东西调用叫独立;

  1. 普通函数被独立的调用
1
2
3
4
5
6
7
// 定义函数
function foo() {
console.log("foo", this);
}

// 独立的调用
foo(); // Window
  1. 函数定义在对象中,但是独立调用
1
2
3
4
5
6
7
8
9
10
11
12
13
const obj = {
name: "fsllala",
say: function () {
console.log("say", this);
}
}

// 对象.属性调用,指向调用对象
// obj.say(); // obj对象

// 独立的调用
const newObj = obj.say;
newObj(); // Window
  1. 高阶函数,独立调用
1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
name: "fsllala",
say: function () {
console.log("say", this);
}
}
function test(fn) {
fn();
}

// 独立的调用
test(obj.say); // Window
  1. 严格模式中,独立调用的函数中的this指向的是 undefined

严格模式中,独立调用的函数中的this指向的是 undefined ; webpack打包啥的可能自动设置为严格模式,所以 独立调用的函数建议不要写this,直接写window;

1
2
3
4
5
6
7
8
9
10
11
12
13
// 严格模式中,独立调用的函数中的this指向的是 undefined ; webpack打包啥的可能自动设置为严格模式,所以 独立调用的函数建议不要写this,直接写window;
"use strict"

function foo() {
console.log(this); //undefined
}
const obj1 = {
name: "obj1",
say: foo,
}

const newObj = obj1.say;
newObj();

隐式绑定(对象环境(属性调用))

也就是它的调用位置中,是通过某个对象发起的函数调用。

当函数被保存为一个对象的属性时,它就可称为这个对象的方法。当一个方法被调用时,this被绑定到这个对象上。如果调用表达式包含一个提取属性的动作(. 或 []),那么它被称为方法调用。下面两种在对象环境中都可以解释的通。

  1. 对象环境指向对象
  2. 谁调用,this指向谁

函数作为对象的属性去调用 例一:

1
2
3
4
5
6
7
8
9
10
11
var name = "全局变量";
var obj = {
name1 :"局部变量",
say: function () {
console.log(this.name);
console.log(this.name1);
}
}
obj.say(); //obj调用,this指向obj; 从function开始找对象环境'{}',找到的是obj,所以this指向obj;
// undefined
// 局部变量

函数作为对象的属性去调用 例二:

1
2
3
4
5
6
7
8
9
10
11
var obj1 = {
a: 222
};
var obj2 = {
a: 111,
fn: function () {
console.log(this.a);
}
}
obj1.fn = obj2.fn;
obj1.fn(); //222 //虽然obj1.fn是从obj2.fn赋值而来,但是调用函数的是obj1,所以this指向obj1。

函数作为对象的属性去调用 例三:

1
2
3
4
5
6
7
8
9
10
var a = {
name: "hello",
b: {
name:"world",
say: function () {
console.log(this.name);
}
}
}
a.b.say(); //world //a.b调用,所以this指向a.b; 从function开始找对象环境'{}',找到的是b,所以this指向b;

函数作为对象的属性去调用 例四:

1
2
3
4
5
6
7
8
9
var obj = {
name:'小明',
foo:foo
}
function foo(){
var name = '小红';
console.log(this.name);
}
obj.foo();//小明 //obj调用,所以this指向obj; foo就是函数foo(){},直接替换了,找对象环境'{}',找到的是obj,所以this指向obj;

函数作为对象的属性去调用 例五:

1
2
3
4
5
6
7
8
9
10
11
12
13
var bj=10;
var obj={
name:"八戒",
age:500,
say:function(){
return function(){
console.log(this);//window
console.log(this.bj);//10
console.log(this.age);//undefined
}
}
}
obj.say()();
  1. 为啥是obj.say()():因为obj.say()执行之后返回了一个函数,并不会执行return函数里面的代码;
  2. 为啥是window:因为obj.say()返回了一个函数,然后在();相当于直接**函数名()**来调用的;所以指向window;

函数作为对象的属性去调用 例六:

1
2
3
4
5
6
7
8
9
10
11
12
var oldObj = {
name: 'first',
say: function () {
return {
name: 'second',
say: function () {
return this;
}
}
}
}
console.log(oldObj.say().say())//{name: 'second', say: ƒ}

显式绑定

通过call、apply、bind明确的绑定了this指向的对象,所以称之为显式绑定;

函数的调用为 xxx();其实这个只是在调用前加上了绑定:xxx.绑定(“绑定对象”);也可以不写绑定对象:xxx.绑定(),效果和xxx()一样;

场景:需求:执行如下函数,并且函数中的this指向obj;

1
2
3
4
5
6
7
const obj = {
nama: "fsllala"
}

function foo(){
console.log("foo函数",this);
}
  • 隐式绑定
1
2
obj.foo=foo;
obj.foo();

如果我们不希望在对象内部包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?

  • 显式绑定
1
2
// 执行函数,并且强制this就是obj对象;
foo.call("obj");

那让我们系统的了解一下他们的用法吧~

call、apply

  • JavaScript所有的函数都可以使用call和apply方法;
  • 第一个参数是相同的,要求传入一个对象;这个对象的作用是什么呢?就是给this准备的,在调用这个函数时,会将this绑定到这个传入的对象上。
  • 后面的参数,apply为数组,call为参数列表;
    • func.apply(thisArg,[argsArray])
    • func.call(thisArg,arg1,arg2,...)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function foo(name, age, height) {
console.log("foo被调用", this);
console.log("打印参数", name, age, height);
}

// ()调用
foo("fsllala", 18, 173);
console.log(".".repeat(34));

/*apply
第一个参数:绑定this
第二个参数:传入额外的参数,以数组的形式
*/

foo.apply('apply', ["fsl", 24, 174]);
console.log(".".repeat(34));

/*call
第一个参数:绑定this
第二个参数:传入额外的参数,以参数列表的形式
*/
foo.call("call","forward",25,175);

this

bind

需求:调用foo时,总是绑定到obj对象身上,并不希望foo对象身上有添加的函数;

  • 可以通过call或apply实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo(name) {
console.log(this);
}

const obj={name:"fsllala"};
// 可以通过call或apply实现
foo.call(obj);

// 但是如果多次调用,每次都要这么写,有点繁琐
foo.call(obj);
foo.call(obj);
foo.call(obj);
foo.call(obj);
foo.call(obj);

那有没有什么简单的写法呢,即:foo.call(obj)简写一下子

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

const obj = { name: "fsllala" };

const bar = foo.bind(obj); //将obj于this绑定,生成一个新的堆,并赋值给bar变量;
bar(); //直接调用即可;
bar();
bar();

可能有人会想:独立函数调用this不指向window了么这不就,其实有个优先级(下文有提到),独立函数的优先级比较低,低于bind绑定;

new绑定 :

new做了什么事情

  1. 创建一个新的空对象 (空实例)(obj)
  2. 将构造函数的显式原型赋值给这个新对象,作为新对象的隐式原型
  3. 构造函数内部的 this,会指向创建出来的新对象 (this 指向第一步的空对象)
  4. 执行函数体的代码块
  5. 返回对象

所以说:构造函数中的 this 会指向创建出来的实例对象

1
2
3
4
5
6
7
8
var TestClass = function () {
this.name = '111';
}
var subClass = new TestClass();
subClass.name = 'cn';
console.log(subClass.name); //cn
var subClass1 = new TestClass();
console.log(subClass1.name) //111
1
2
3
4
5
6
function Foo(name,age){
this.name = name;
this.age = age;
}
var foo = new Foo('zs',18);
console.log(foo.name)//zs

this优先级

如果new、bind、apply、call、隐式绑定和默认绑定同时出发多个的话,this会指向谁呢?

这里先说一下结论,下面做验证,优先级排名为:

  1. new
  2. bind
  3. apply/call
  4. 隐式绑定
  5. 默认绑定

接下来可通过代码进行如上验证:

  • 显示优先级(call/apply)高于隐式优先级
1
2
3
4
5
6
7
8
9
10
// 1.显示绑定>隐式绑定
function foo() {
console.log(this);
}

let obj = {
say: foo
}
obj.say(); // {say: ƒ}-->obj对象
obj.say.call("lala"); // String {'lala'} -->string包装类型
  • 显示优先级(bind)高于隐式优先级
1
2
3
4
5
6
7
8
9
function foo() {
console.log(this);
}
const bar = foo.bind("aaa"); // -->类似于深拷贝了一份foo
let obj = {
say: bar
}

obj.say(); // String {'aaa'} -->string包装类型
  • new优先级高于隐式优先级
1
2
3
4
5
6
7
8
let obj = {
name:"fsllala",
say: function () {
console.log(this);
}
}

new obj.say(); // say {} -->一个空对象
  • new不能和call/apply一起使用
  • new优先级高于bind优先级
1
2
3
4
5
6
function foo(){
console.log(this);
}
const bar = foo.bind("bbb");
// bar(); // String {'bbb'}
new bar(); // foo {} -->是个空对象,所以new高级bind
  • bind比apply/call优先级高
1
2
3
4
5
6
7
function foo(){
console.log(this);
}

const bar = foo.bind("ccc");

bar.call("ddd"); // String {'ccc'}

事件对象:

在 DOM 事件中使用 this,this 指向了触发事件的 DOM 元素本身

1
2
3
li.onclick = function(){
console.log(this); //li
}

箭头函数:

详见:箭头函数

setTimeout与setInterval:

  • setInterval和setTimeout中传入函数时,函数中的this会指向window对象

  • 可以在定(延)时器外面var that = this;来解决这个问题

1
2
3
setTimeout(function () {
console.log(this)
}) //window
  1. 这是最最常用的常见的定时器用法,回调函数里的this指向的是window。
  2. 我的理解是:由于setTimeout属于宏任务,它的回调在延时之后才进入到主线程执行,而函数执行的时候才明确 this 的指向。执行的时候,由于没有设置内部this的指向。相当于是普通函数调用。所以会默认指向window
1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
age: 10,
getage: function () {
console.log(this.age)
}
}

setTimeout(obj.getage, 1000) // undefined

setTimeout(function () {
obj.getage() // 10
}, 1000)
  1. 第一个setTimeout,执行obj.getage 之后,相当于setTimeout的回调是一个匿名函数,执行的时候,函数内部未设置this的指向。相当于是普通函数调用。所以this默认指向window,所以结果是undefined。
  2. 第二个setTimeout,传给setTimeout的也是一个匿名回调函数,执行匿名函数,执行到 obj.getage() 的时候,getage函数里的this,指向的就是obj了,所以能打印出10。还是遵循 谁调用产生 this指针的函数,this就指向谁的规则

参考文献