js的this指向问题

36 阅读5分钟

一、规则

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

二、绑定方式

主要分为默认绑定、隐式绑定、显示绑定、new绑定

而js内置函数的绑定规则属于特殊规则,根据不同的函数判断(一般根据经验判断)

1.1 默认绑定

  • 没有绑定到任何对象上,独立的函数调用——指向window
  • 绑定到对象上,但是独立调用——指向window
  • 严格模式下,独立调用指向的是undefined
// 案例1
function foo() {
  console.log(this)
}
// 独立函数调用,默认绑定,指向window
foo()


// 案例2
function foo1() {
  console.log(this)
}
function foo2() {
  console.log(this)
  foo1()
}
function foo3() {
  console.log(this)
  foo2()
}
foo3()


// 案例3
var obj_3 = {
  name_3: "yz1",
  foo_3: function() {
    console.log(this)
  }
}
var bar_3 = obj.foo_3
bar_3() // 独立函数调用,window
obj_3.foo_3() // obj调用方式,指向obj


// 案例4
function foo_4() {
  console.log(this)
}
var obj_4 = {
  name: "yz2",
  foo_4: foo_4
}
var bar_4 = obj.foo_4
bar_4() // 独立函数调用,window


// 案例5
function foo_5() {
  function bar_5() {
    console.log(this)
  }
  return bar_5
}
var f = foo_5()
f() //独立调用 window
var obj_5 = {
  name_5: "yz3",
  sleeping: f
}
obj_5.sleeping() // 返回obj对象,隐式绑定

1.2 隐式绑定

  • 通过某个对象进行调用——指向相应的对象
// 案例一
function foo1() {
  console.log(this)
}

var obj1 = {
  name: "yz",
  foo1: foo1
}

obj1.foo1() // 返回obj对象,隐式绑定

// 案例二
var obj2 = {
  name: "yz",
  sleeping: function() {
    console.log(this.name + " is sleeping")
  },
  smiling: function() {
    console.log(this)
    console.log(this.name + " is smiling")
  },
}

obj2.sleeping()
var fn2 = obj2.smiling
fn2() //独立函数调用 window

// 案例三
var obj3_1 = {
  name: 'obj_1',
  foo3_1: function() {
    console.log(this)
  }
}

var obj3_2 = {
  name: "obj_2",
  fn: obj3_1.foo3_1
}
obj3_2.fn() //返回obj2

1.3 显式绑定

  • 通过call、apply、bind调用绑定this
function foo() {
  console.log(this)
}

// 函数自带一个call函数,执行
foo()
/**
 * call/apply调用不同在于this的绑定的不同
 * foo指向window
 */

var obj = {
  name: "hello"
}
// 显示调用,call/apply可以指定this的绑定对象的
foo.call(obj) // hello
foo.apply(obj) // hello

/**
 * call与apply的区别
 * 1.参数不同
 */

function sum(num1, num2, num3) {
  console.log(num1 + num2 + num3, this)
}

sum.call("call", 20, 30, 40)
sum.apply("apply", [20, 30, 40])

/**
 * call与apply可以明确指定this的绑定,实现显示绑定
 */
  • 如果绑定的对象为null、undefined, 会转化成默认绑定
    • 非严格模式:指向window
    • 严格模式下:指向null、undefined
function foo() {
  console.log("foo", this);
}
foo.apply("abc");
foo.apply(null);
foo.apply(undefined);

1.4 new绑定

  • 通过new关键字进行绑定——指向new新生成的对象
function foo() {
  console.log('foo函数', this);
  this.name = 'yz';
}

/**
 * new操作
 * 1.创建新的空对象
 * 2.将this指向这个空对象
 * 3.执行函数体重的代码
 * 4.没有显示返回非空对象时,默认返回这个对象
 */

new foo();

1.5 内置函数绑定(一般根据经验)

// 内置函数(第三方库):根据经验
// 1.定时器
setTimeout(function () {
  console.log("定时器", this);
}, 1000);


// 2.按钮的点击监听
var btnEl = document.querySelector("button");
btnEl.onclick = function () {
  console.log("btn的点击", this);
};


btnEl.addEventListener("click", function () {
  console.log("listener", this);
});


// 3.forEach
var names = ["aaa", "bbb"];
names.forEach(function (item) {
  console.log("forEach", this);
});
names.forEach(function (item) {
  console.log("forEach", this);
}, "aaa");

三、绑定规则的优先级

  1. 默认绑定优先级最低
  2. 显式>隐式
var obj = {
  name: 'obj',
  foo: function() {
    console.log(this)
  }
}
console.log("直接输出")
obj.foo()
// 1.显示绑定优于隐式绑定
console.log("既有显示绑定也有隐式绑定")
obj.foo.call("aaa") //绑定aaa


// 2.bind 隐式绑定
var bar = obj.foo.bind("bbb")
bar() //绑定bbb


// 3.更明显的比较,bind显示绑定优先级高于隐式绑定
function foo() {
  console.log(this)
}


var obj1 = {
  name: "obj1",
  foo: foo.bind("ccc")
}
obj1.foo() //绑定ccc

3. new绑定>隐式

// new 绑定高于隐式绑定
var obj = {
  name: "obj",
  foo: function() {
    console.log(this)
  }
}


var f = new obj.foo() // 绑定foo对象

4. new绑定>bind绑定(new不能和apply、call一起使用)

// new优先级高于显示绑定
function foo1() {
  console.log(this)
}
var bar = foo1.bind("aaa")
var ff = new bar() // 绑定foo1对象

5. bind>apply/call 6. 总结: new>显示(bind>apply/call)>隐式>默认

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

// 1.显示绑定高于隐式绑定
// 1.1 apply
var obj = { foo };
obj.foo.apply("abc");
// 1.2 bind
var bar = foo.bind("aaa");
var obj = {
  name: "yz",
  baz: bar,
};
obj.baz();


// 2.new绑定优先级高于隐式绑定
var obj2 = {
  name: "yz",
  age: 18,
  foo: function () {
    console.log("foo", this);
  },
};


new obj2.foo();


// 3.new/显示
// 3.1 new不可以和apply/call一起使用
// 3.2 new优先级高于bind
var bindFn = foo.bind("aaa");
new bindFn();


// 4.bind/apply优先级
bindFn.apply("apply");
bindFn.call("call");

四、箭头函数

4.1 与普通函数的区别

  • 不会绑定this、arguments属性
  • 不能作为构造函数来使用( 不能和new一起使用

4.2 优化代码规则

  1. 果箭头函数只有一个参数,那么()可以省略
  2. 如果函数体重只有一行执行代码,那么{}可以省略
  3. 只有一行代码时,这行代码的表达式结果会作为函数的返回值默认返回
  4. 如果默认返回值是一个对象,那么这个对象必须加()
var names = ["aaa", "bbb", "ccc"];

// 优化一:如果箭头函数只有一个参数,那么()可以省略
names.forEach((item, index, arr) => {
  console.log(item, index, arr);
});
names.forEach((item) => {
  console.log(item);
});

// 优化二:如果函数体重只有一行执行代码,那么{}可以省略
// 一行代码中不能带return
names.forEach((item) => console.log(item));

// 优化三:只有一行代码时,这行代码的表达式结果会作为函数的返回值默认返回
var nums = [2, 3, 6, 9, 8, 7, 4, 1, 5];
var newNum = nums.filter((item) => item % 2 === 0);
console.log(newNum);

// 优化四:如果默认返回值是一个对象,那么这个对象必须加()
// 注意:react中redux经常使用
// var arrFn = () => [123, "abc"];
var arrFn = () => ({ name: "yz" });
console.log(arrFn());

4.3 this绑定

  • 箭头函数中 没有this ,因此输出this的时候,会去 上层作用域 去找
var bar = () => {
  console.log("bar", this);
};
bar();
// 通过apply调用,也是没有this
bar.apply("aaa"); //window
  • this的查找规则
var obj = {
  name: "obj",
  foo: function () {
    // foo函数内部有this
    var bar = () => {
      // bar函数内部没有this
      console.log("bar", this);
    };
    return bar;
  },
};
var fn = obj.foo();
fn.apply("bbb"); // obj


var message = "global message";
var obj2 = {
  name: "obj",
  message: "obj2 message",
  foo: () => {
    // foo函数内部有this
    var bar = () => {
      // bar函数内部没有this
      console.log("bar", this);
      console.log(message);
    };
    return bar;
  },
};
var fn2 = obj2.foo();
fn2.apply("ccc"); // window

五、关于this的面试题

面试题一

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

面试题二

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
person1.foo1.call(person2); // person2

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

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

面试题三

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.foo2.call(person2); // person1

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

面试题四

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