前端面试题|前端面试题之JavaScript【this指向】

JavaScript this指向

  • 全局环境下:this 始终指向全局对象(window), 无论是否严格模式
    console.log(this.document === document); // true// 在浏览器中,全局对象为 window 对象: console.log(this === window); // truethis.a = 37; console.log(window.a); // 37

  • 普通函数调用:非严格模式下:指向全局对象 :window; 严格模式:this是undefined
    function f1(){ return this; // 非严格模式:普通函数的this指向window } f1() === window; // true

    function f1(){ "use strict"; // 这里是严格模式 return this; // 严格模式:普通函数的this是undefined } f1() === window; // false

    // let 声明的变量 let username = 'username' function fn(){ alert(this.username); // undefinedlet声明的变量不在window上 } fn();

    // var 声明的变量 var username = 'username' function fn(){ alert(this.username); // username } fu();

    // 直接挂载在 window 上的变量 window.username = 'username' function fn(){ alert(this.username); // username } fn(); //可以理解为 window.fn();

  • 对象函数调用:哪个对象函数调用,this就指向哪里
    window.b=2222 let obj={ a:111, fn:function(){ alert(this.a); //111 alert(this.b); //undefined } } obj.fn(); // 此时的 this 指向对象 obj; this.a 相当于 obj.a, this.b 相当于 obj.b ; // this.a = 'username', obj没有 b 属性,所以 this.b = undifined

  • 构造函数调用:this指向实例对象
    // 例1: function Person(age, name) { this.age = age; this.name = name console.log(this)// 此处 this 分别指向 Person 的实例对象 p1 p2 console.log(this.age) console.log(this.name) } var p1 = new Person(18, 'zs')// 输出:18, zs var p2 = new Person(18, 'ww')// 输出:18, ww// 例2: let TestClass = function() { this.name = '111'; } let subClass = new TestClass(); subClass.name = 'cn'; console.log( subClass.name ); // cn let subClass_1 = new TestClass(); console.log( subClass_1.name ) // 111

    new() 方法做了什么
    (1) 创建一个新对象
    (2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象) ;
    (3) 执行构造函数中的代码(为这个新对象添加属性) ;
    (4) 返回新对象。
    new 操作符
    在有上面的基础概念的介绍之后,在加上new操作符,我们就能完成传统面向对象的class + new的方式创建对象,在JavaScript中,我们将这类方式成为Pseudoclassical。
    基于上面的例子,我们执行如下代码
    var obj = new Base();

    我们在Javascript引擎中看到的对象模型:
    前端面试题|前端面试题之JavaScript【this指向】
    文章图片

    new操作符具体干了什么呢? 其实很简单,就干了三件事情。
    var obj= {}; // 创建一个新对象 obj.__proto__ = Base.prototype; // 新对象的 __proto__(隐式原型)指向Base函数对象的prototype(显式原型)原型对象 Base.call(obj); // 将Base的this指针利用 call()方法替换成obj,然后调用Base函数,给obj对象赋值了一个id成员变量,这个成员变量的值是”base”

    如果我们给Base.prototype的对象添加一些函数会有什么效果呢?
    例如代码如下:
    Base.prototype.toString = function() { return this.id; }

    那么当我们使用new创建一个新对象的时候,根据__proto__的特性,toString这个方法也可以做新对象的方法被访问到。于是我们看到了:
    构造函数中,我们来设置‘类’的成员变量(例如:例子中的id),构造子对象prototype中我们来设置‘类’的公共方法。于是通过函数对象和Javascript特有的__proto__与prototype成员及new操作符,模拟出类和类实例化的效果。
  • 定时器函数调用:this 指向 window
    setInterval(function () { console.log(this); // window }, 1000);

  • 事件绑定调用:this 指向 绑定事件的对象
    > var oBtn = document.getElementById("btn"); oBtn.onclick = function() { console.log(this); // btn }

  • apply和call调用【修改this指向】
    var Person = { name:"lixue", age:21 } function fn(x,y){ console.log(x+","+y); // hh20 console.log(this); // this 指向 Person console.log(this.name); // lixue console.log(this.age); // 21 } fn.call(Person,"hh",20); fn.apply(Person,["hh",20]);

  • 箭头函数调用:自身没有this,箭头函数的this是父级所在的执行上下文中的this
    注意:简单对象(非函数)是没有执行上下文的!所以在定义对象的时候,定义对象属性,里面的this指向的一般是全局,或者这个对象所在的那个环境中的this。
    实际原因: 箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。
    // 例1: function Person() { this.age = 0; setInterval(() => { this.age++; // this的指向是 p 实例对象,执行上下文是Person() }, 3000); }var p = new Person(); // 输出 0 1 2 ..... 间隔三秒// 例2: var b = 'BBBB'; var obj = { b : 'bbbb' say:()=>{ console.log(this.b)// this 的指向是上级对象obj所在的执行上下文:window,所以输出的是:BBBB } } obj.say()// 输出 BBBB

  • call、bind、apply区别
    • call:第一个参数是要绑定给this的值,后面传入的是一个参数列表 list 。当第一个参数为null、undefined的时候,默认指向window。
      特点:从第二个参数开始是一个参数列表 list
      var arr = [1, 2, 3, 89, 46] var max = Math.max.call(null, arr[0], arr[1], arr[2], arr[3], arr[4]) // 输出 89;因为call方法的第一个参数为null,则 this 指向 window,从window中找 arr 变量

    • apply:第一个参数是要绑定给this的值,第二个参数是一个参数数组。当第一个参数为null、undefined的时候,默认指向window。
      特点:从第二个参数是一个参数数组
      var arr = [1, 2, 3, 89, 46] var max = Math.max.call(null, arr) // 输出 89;因为call方法的第一个参数为null,则 this 指向 window,从window中找 arr 变量

      示例:
      var obj = { message: 'My name is: ' }function getName(firstName, lastName) { // 原本的this指向window,但是call/apply改变后,此时的this指向 obj,所以 this.message 相当于 obj.message = My name is: console.log(this.message + firstName + ' ' + lastName) }getName.apply(obj, ['Dot', 'Dolby']) // My name is: Dot Dolby getName.apply(obj, 'Dot', 'Dolby') // My name is: Dot Dolby

      var Person1= function () { this.name = 'Dot'; } var Person2 = function () { this.getname = function () { console.log(this.name); } Person1.call(this); // 改变this指向,使得Person2的this指向 Person1;相当于Person2 拥有了Person1 的所有属性,所以this.name = 'Dot' } var person = new Person2(); person.getname(); // Dot

    • bind:第一个参数是this的指向,第二个参数开始是接收的参数列表。
      特点:bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数以及bind接收的参数列表的使用。。
      返会的函数
      var obj = { name: 'Dot' }function printName() { console.log(this.name)// 函数本身的this指向没有被改变,依然指向 window } var dot = printName.bind(obj)// this指向 obj console.log(dot)// function () { … } dot()// Dot printName(); // undefiined

      【前端面试题|前端面试题之JavaScript【this指向】】参数的使用
      function fn(a, b, c) { console.log(a, b, c); } var fn1 = fn.bind(null, 'Dot'); //this 指向 windowfn('A', 'B', 'C'); // A B C fn1('A', 'B', 'C'); // Dot A B fn1('B', 'C'); // Dot B C fn.call(null, 'Dot'); // Dot undefined undefined

    推荐阅读