彻底搞懂js中的this关键字

99 阅读5分钟

在常见的编程语言中,几乎都有this这个关键字(Objective-C中使用的是self)

常见面向对象的编程语言中,比如Java、C++、Swift、Dart等等一系列语言中,this通常只会出现在类的方法中。

也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象

但是js中的this的值更为的多样性,也更为的灵活

this的作用

// 这个对象的每一个方法在调用的时候都希望获取obj.name
// 但是如果我们将obj修改为foo的时候,就意味着函数内部都需要进行对应的修改,十分的不方便
var obj = {
  name: "klaus",
  running: function() {
    console.log(obj.name + " running");
  },
  eating: function() {
    console.log(obj.name + " eating");
  },
  studying: function() {
    console.log(obj.name + " studying");
  }
}
// 此时我们就可以使用this来进行替换
// this表示的是当前方法的调用者
var obj = {
  name: "klaus",
  running: function() {
    console.log(this.name + " running");
  },
  eating: function() {
    console.log(this.name + " eating");
  },
  studying: function() {
    console.log(this.name + " studying");
  }
}

绑定规则

this默认指向的就是当前调用者,所以this的具体值只能在运行的时候,才会知道

编译的时候,是没有办法获取到this的值

基本规则

默认规则

独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用

此时的this就是默认值

// 独立函数调用有可以分为以下3种情况

// 1. 普通函数调用
function foo() {
  console.log(this); // => window
}

foo();

// --------------------------------------------------

// 2. 函数调用链
// 2.案例二:
function test1() {
  console.log(this); // => window
  test2();
}

function test2() {
  console.log(this); // => window
  test3()
}

function test3() {
  console.log(this); // => window
}
test1();

// --------------------------------------------------

// 3. 函数作为参数传递
function foo(func) {
  func()
}

function bar() {
  console.log(this); // => window
}

foo(bar);

隐式绑定

函数被某一个对象调用,

当前函数是谁调用的,那么这个函数中的this就是谁

// 1. 通过对象调用函数
function foo() {
  console.log(this); // => obj对象
}

var obj = {
  name: "klaus",
  foo: foo
}

obj.foo();

// --------------------------------------------------

// 2. 对象的链式调用
function foo() {
  console.log(this); // => obj对象
}

var obj1 = {
  name: "obj1",
  foo: foo
}

var obj2 = {
  name: "obj2",
  obj1: obj1
}

obj2.obj1.foo(); // <=> const tmp = obj2.obj1; tmp.foo();

显示绑定

// call 和 apply方法
function foo() {
  console.log(this);
}

foo.call(window); // window
foo.call({name: "klaus"}); // => {name: "klaus"}
// call和apply的第一个参数都是对象,如果不是对象会隐式转换为对象后在进行this修改
// call和apply方法的区别: 
//   call方法自第二个参数传递参数,有几个就传几个 xxx.call(this, param1, param2, ...)
//   apply方法自第二个参数传递参数,格式为数组,也就是只能有2个参数 xxx.apply(this, [param1, param2, ....)
foo.call(123); // Number{ [[PrimitiveValue]] } 

// --------------------------------------------------

// bind方法
function foo() {
  console.log(this);
}

var obj = {
  name: "klaus"
}

// bind方法会返回一个新的方法,在这个新的方法中this被修改
// 这是和call和apply的最大区别,call和apply是直接修改this值并立即调用函数
// bind是修改this值并返回一个新的函数,并不会立即调用
var bar = foo.bind(obj);

bar(); // => obj对象

内置函数

setTimeout(function() { // => setTimeout,serInterval函数中的this是window
  console.log(this); // => window
}, 1000);

// --------------------------------------------------

var names = ["abc", "cba", "nba"];
names.forEach(function(item) { // => 数组的回调函数(forEach,filter,map...)中的this是window
  console.log(this); // => window x 3
});

var names = ["abc", "cba", "nba"];
var obj = {name: "klaus"};
names.forEach(function(item) {
  console.log(this); // => obj对象 x 3
}, obj);  // => 数组的回调函数中一般都有第二个参数,用于指定当前回调函数的this值

// --------------------------------------------------

var box = document.querySelector(".box");
box.onclick = function() { // 浏览器事件中的this,为触发事件的dom元素对象
  console.log(this); // => box对象
}

new关键字

// 创建Person
function Person(name) {
  console.log(this); // => Person {}
  this.name = name; // Person {name: "klaus"}
}

/*
	使用new关键字,创建实例对象的时候
	1. 创建一个全新的对象;
	2. 将this指向这个对象
  3. 执行构造函数中对应的代码
  4. 将这个对象返回
*/
var p = new Person("klaus");
console.log(p); // => Person {name: "klaus"} => 所以类中的this就是当前类初始化后形成的实例对象

规则优先级

new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

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

var obj1 = {
  name: "obj1",
  foo: foo
}

var obj2 = {
  name: "obj2",
  foo: foo
}

// 隐式绑定和显示绑定同时存在, 显示绑定优先级更高
obj1.foo.call(obj2); // => obj2对象
// <=> const tmp = obj1.foo; tmp.call(obj2); 
function foo() {
  console.log(this);
}

var obj = {
  name: "klaus",
  foo: foo
}

new obj.foo(); // => foo对象
// <=> const tmp = obj.foo; new tmp();
function foo() {  console.log(this);}var obj = {  name: "obj"}// new关键字的优先级比bind函数要高var bar = foo.bind(obj);var foo = new bar(); // => foo对象// --------------------------------------------------function foo() {  console.log(this);}var obj = {  name: "obj"}// call和apply方法是js的内置方法,不可以使用new关键字进行实例化,所以会报错var foo = new foo.call(obj); // error

例外规则

忽略显示绑定

如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则

function foo() {  console.log(this);}var obj = {  name: "klaus"}foo.call(obj); // => obj对象foo.call(null); // => windowfoo.call(undefined); // => windowvar bar = foo.bind(null);bar(); // => window

间接函数引用

function foo() {  console.log(this);}var obj1 = {  name: "obj1",  foo: foo}; var obj2 = {  name: "obj2"}obj1.foo(); // => obj1对象/*	num = 2 // => 返回结果为2, 这也是js可以连续赋值的基础	num1 = num2 = 2*/(obj2.foo = obj1.foo)();  // => window (obj2.foo = obj1.foo)() <=> foo()// obj2.foo = obj1.foo => 结果是foo函数对应的引用地址

箭头函数

ES6的箭头函数,内部是没有this的,其是根据外层作用域来决定this。

// ES5var obj = {  data: [],  getData: function() {    var _this = this; // setTimeout中的this是window,所以需要在外层保存this变量    setTimeout(function() {       // 模拟异步请求      var res = ["abc", "cba", "nba"];      _this.data.push(...res);    }, 1000);  }}obj.getData();// ------------------------------------------------// ES6var obj = {  data: [],  getData: function() {    setTimeout(() => { // 内部没有this,往上一层寻找this的值,如果上一层没有,就逐层上找,直到顶层this,即window      // 模拟异步请求      var res = ["abc", "cba", "nba"];      this.data.push(...res);    }, 1000);  }}obj.getData();

面试题

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(); // => person1person1.foo1.call(person2);  // => person2person1.foo2(); // => windowperson1.foo2.call(person2); // => windowperson1.foo3()(); // => windowperson1.foo3.call(person2)(); // => windowperson1.foo3().call(person2); // => person2/*	var person1 = {    name: 'person1',    foo4: function () {    	// 2. 到箭头函数的上一级进行查找,也就是person1.foo4()中,其中的this是person1       return () => { // 1. person1.foo4()() => 监听函数内部没有this        console.log(this.name)      }    }  }*/person1.foo4()(); // => person1person1.foo4.call(person2)(); // => person2person1.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')person1.obj.foo2()() // => obj对象// person1.obj.foo2()返回箭头函数,所以上一级是person1.obj.foo2方法,此时其内部this是person1.obj