JavaScript中this指向

153 阅读4分钟

什么是this

MDN 上是这样说的:在绝大多数情况下,函数的调用方式决定了 this 的值

ECMAScript 规范中这样写:this 关键字执行为当前执行环境的 ThisBinding。

按照自己的理解翻译一下:在 JavaScript 中,this 的指向是调用时决定的,而不是创建时决定的,这就会导致 this 的指向会让人迷惑,简单来说,this 具有运行期绑定的特性。

死记硬背以下的规则

1、在函数体中,非显示或隐式地简单调用函数时,在严格模式下,函数内的this会被绑定到undefined上,在非严格模式下则会绑定到全局对象window/global上

2、一般使用new方法调用构造函数时,钩爪函数内的this会被绑定到新创建的对象上

3、一般通过call/apply/bind方法显式调用函数时,函数体内的this会被绑定指定参数的对象上

4、一般通过上下文都一项调用函数时,函数体内的this会被绑定到该对象上

5、在箭头函数中,this的指向由外层(函数或全局)作用域来决定的

箭头函数中的this

  • 所有的箭头函数都没有 this,都是指向外层的当前函数所在的上下文的 this
  • 为什么箭头函数没有自己的 this,因为箭头函数中的 this在编译的时候会被变量替代
// 编译前书写的箭头函数
let test = {
  foo: "apple",
  getFoo() {
    return () => {
      return this.foo;
    };
  },
};

// 编译后
let test = {
  foo: "apple",
  getFoo() {
    let _this = this;
    return function () {
      return _this.foo;
    };
  },
};

对于普通函数来说,内部的this指向函数运行时所在的对象,但是这一点对箭头函数不成立。它没有自己的this对象,内部的this就是定义时上层作用域中的this。也就是说,箭头函数内部的this指向是固定的,相比之下,普通函数的this指向是可变的

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数
  setInterval(() => this.s1++, 1000);
  // 普通函数
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0

this的优先级

new > 显式绑定 > 隐式绑定

bind、call、apply、箭头函数的区别

  • 使用apply或call方法,那么this指向他们的第一个参数,apply的第二个参数是一个参数数组,call的第二个及其以后的参数都是数组里面的元素,就是说要全部列举出来
  • 都是用来改变函数的this对象的指向的;
  • 第一个参数都是this要指向的对象;
  • 都可以利用后续参数传参;
  • bind是返回对应函数,便于稍后调用,apply、call是立即调用;
  • 箭头函数:没有自己的this值,箭头函数内的this值继承自外围作用域,在箭头函数中调用 this 时,仅仅是简单的沿着作用域链向上寻找,找到最近的一个 this 拿来使用
var name = "佚名";

var age = 20;

//global.name

//global.age

var p1 = {

      name: "张三",

     age: 12,

     func: function(){

         console.log(`姓名:${this.name},年龄:${this.age}`)

     }

}

var p2 = {

name: "李四",

age: 15

}

p1.func();  //姓名:张三,年龄:12

p1.func.call();  //姓名:佚名,年龄:20

p1.func.apply(p2);  //姓名:李四,年龄:15

p1.func.bind(p2)(); //姓名:李四,年龄:15

this面试题(所有题目都源于网络)

第一题

var name = "222";
var a = {
  name: "111",
  say: function () {
    console.log(this.name);
  },
};
var fun = a.say;
fun();
a.say();
var b = {
  name: "333",
  say: function (fn) {
    fn(); 
  },
};

b.say(a.say);
b.say = a.say;
b.say();

答案

var name = "222";
var a = {
  name: "111",
  say: function () {
    console.log(this.name);
  },
};
var fun = a.say;
fun(); // 此时fun处于全局环境中,也就是全局环境调用这个方法,谁调用this指向谁,所以打印 222
// fun() === fun.call(window)  fun相当于是语法糖
a.say(); // 此时函数处于a对象中,所以 this指向 a对象 所以打印 111
// a.say() === a.say.call(a)
var b = {
  name: "333",
  say: function (fn) {
    fn(); // 此时fn执行没有其他的this 绑定,
    // fn() === fn.call(window)
    // a.say() === fn() === fn.call(window) === a.say.call(window)
  },
};

b.say(a.say); // 打印 222
b.say = a.say;
// b.say = a.say 相当于
// var b = {
//     name: '333',
//     say:function(){
//         console.log(this.name)
//     }
// }
b.say(); // 打印 333

第二题

let obj = {
  fn: (function () {
    return function () {
      console.log(this);
    };
  })(),
};
obj.fn();
let fn = obj.fn;
fn();

答案

obj.fn(); // this指向的是obj ,所以输出 {fn:function(){}} 
let fn = obj.fn; 
fn(); // 此时的fn 属于全局,相当于把函数赋值给全局变量,所以 this 是window

第三题

var fullName = "language";
var obj = {
  fullName: "javascript",
  prop: {
    getFullName: function () {
      return this.fullName;
    },
  },
};
console.log(obj.prop.getFullName());
var test = obj.prop.getFullName;
console.log(test());

答案

console.log(obj.prop.getFullName()); //=>this:obj.prop =>obj.prop.fullName =>undefined 
var test = obj.prop.getFullName; 
console.log(test()); //=>this:window =>window.fullName =>'language' */

第四题

var name = "window";
var Tom = {
  name: "Tom",
  show: function () {
    console.log(this.name);
  },
  wait: function () {
    var fun = this.show;
    fun();
  },
};
Tom.wait();

答案

var Tom = {
  name: "Tom",
  show: function () {
    // this->window
    console.log(this.name); //=>'window'
  },
  wait: function () {
    // this->Tom
    var fun = this.show;
    fun();
    // 此时fn执行没有其他的this 绑定,
    // fn() === fn.call(window)
  },
};

第五题

window.val = 1;
var json = {
  val: 10,
  dbl: function () {
    this.val *= 2;
  },
};
json.dbl();
var dbl = json.dbl;
dbl();
json.dbl.call(window);
alert(window.val + json.val);  //24

答案

json.dbl();
//->this:json  ->json.val=json.val*2  ->json.val=20
var dbl = json.dbl;
dbl();
//->this:window ->window.val=window.val*2 ->window.val=2
json.dbl.call(window);
//->this:window ->window.val=window.val*2 ->window.val=4
alert(window.val + json.val); //=>"24"

第六题

(function () {
  var val = 1;
  var json = {
    val: 10,
    dbl: function () {
      val *= 2;
    },
  };
  json.dbl();
  alert(json.val + val);
})();  //12

答案

(function () {
  var val = 1; //->2
  var json = {
    val: 10,
    dbl: function () {
      // this->json
      val *= 2; //val=val*2  此处的val是变量  json.val是对象的属性
    },
  };
  json.dbl();
  alert(json.val + val); //=>"12"
})();