第四节:JS中的this指向详解(规则总结、优先级分析、特殊情况)和面试题剖析

一. 四大规则总结

1. 背景说明

(1). 为什么需要this?

   从某些角度来说, 开发中如果没有this, 很多的问题我们也是有解决方案, 但是没有this, 会让我们编写代码变得非常的不方便。
var obj1 = {
  name: "ypf1",
  eating: function () {
    console.log(obj1.name + "在吃东西");
  },
  running: function () {
    console.log(obj1.name + "在跑步");
  },
  studying: function () {
    console.log(obj1.name + "在学习");
  },
};
var obj2 = {
  name: "ypf2",
  eating: function () {
    console.log(this.name + "在吃东西");
  },
  running: function () {
    console.log(this.name + "在跑步");
  },
  studying: function () {
    console.log(this.name + "在学习");
  },
};

// 调用
obj1.eating();
obj2.eating();
View Code

(2). this的全局指向?

/* 
    在大多数情况下,this都是出现在函数中的。
    在全局作用域下:
    1. 浏览器中运行,this是window
    2. node环境中,this是空对象 {}
*/

console.log(this);

(3). 抛砖引玉

  this指向什么,与函数所在的位置无关,与函数被调用的方式有关系, 从而引出后续的几种绑定规则。
function foo() {
  console.log(this);
}

// 1. 直接调用 (指向window)
foo();

//2. 创建1个对象,对象中的函数指向foo
var obj1 = {
  name: "ypf",
  foo: foo,
};
obj1.foo(); //指向obj1

//3. apply调用
foo.apply("abc"); //指向abc
View Code

2. 规则1-默认绑定

   默认绑定:也就是独立函数的调用,this都是指向window

   何为独立函数调用?  我们可以理解成函数没有绑定到某个对象上的调用; 即光秃秃的1个函数, foo()

   注:与函数定义的位置没有关系,只看调用位置

   特殊情况:如果是严格模式,或者react中的babel转换(默认严格模式), 默认绑定的windows为undefined 

// 1. 案例1 (指向window)
{
  function foo() {
    console.log(this);
  }
  foo();
}

// 2. 案例2 (指向window)
{
  function foo1() {
    console.log("-----------------------", this);
  }
  function foo2() {
    console.log("-----------------------", this);
    foo1();
  }
  function foo3() {
    console.log("-----------------------", this);
    foo2();
  }
  foo3(); //输出了三个window对象
}

//3. 案例3
{
  var obj = {
    name: "ypf",
    foo: function () {
      console.log(this);
    },
  };
  var bar = obj.foo;
  bar(); //window (说明只与调用位置有关)
}

//4. 案例4
{
  function foo() {
    console.log(this);
  }
  var obj = {
    name: "ypf",
    foo: foo,
  };
  var bar = foo;
  bar(); //window (说明只与调用位置有关)
}

// 5. 案例5
{
  function foo() {
    function bar() {
      console.log(this);
    }
    return bar;
  }
  var fn = foo();
  fn(); //window (说明只与调用位置有关)
}
View Code

3. 规则2-隐式绑定

  this指向这个对象obj

    比较常见的调用方式是通过某个对象进行调用的:也就是它的调用位置中,是通过某个对象发起的函数调用。

    即:Object.fn(),  Object对象就会被js引擎绑定到fn函数中的this里面

// 公用函数foo
function foo() {
  console.log(this);
}

// 案例1
{
  console.log("-------------案例1---------------");
  var obj = {
    name: "ypf",
    foo: foo,
  };
  obj.foo(); //this指向obj  { name: 'ypf', foo: [Function: foo] }
}

// 案例2
{
  console.log("-------------案例2---------------");
  var obj = {
    name: "ypf",
    eating: function () {
      console.log(this, this.name);
    },
  };
  obj.eating(); //this指向obj  { name: 'ypf', foo: [Function: foo] }
  {
    var fn = obj.eating;
    fn(); //这种模式属于独立函数调用,this指向window,this.name拿不到值,undefined
  }
}

// 案例3
{
  console.log("-------------案例3---------------");
  var obj1 = {
    name: "obj1",
    foo: function () {
      console.log(this);
    },
  };
  var obj2 = {
    name: "obj2",
    bar: obj1.foo,
  };
  obj2.bar(); //this指向obj2,充分说明哪个对象点出来的,this就指向哪个对象
}
View Code

4. 规则3-apply/call/bind显式绑定

 显式绑定 (call、apply绑定谁,则this就指向谁), call和apply在执行函数时,是可以明确的绑定this, 这个绑定规则称之为显示绑定。
// 前置函数foo和obj对象
function foo() {
  console.log(this);
}
var obj = {
  name: "ypf",
};

// 案例1. call和apply可以直接指向this的绑定对象
{
  console.log("--------------------案例1------------------------");
  foo.call(); //不指定的时候,this指向window
  foo.apply(); //不指定的时候,this指向window
  // 下面绑定对象
  foo.call(obj); //this指向obj
  foo.apply(obj); //this指向obj
  foo.call("abc"); //this指向abc
  foo.apply("abc"); //this指向abc
}

// 案例2. call和apply用法的区别
/* 
  call:传递参数的形式和被调用函数的形式相同
  apply:以数组的形式传递进去
*/

{
  console.log("--------------------案例2------------------------");
  function sum(num1, num2, num3) {
    console.log(num1 + num2 + num3, this);
  }
  sum.call("ypf1", 10, 20, 30); //this指向ypf1,打印结果为60
  sum.apply("ypf2", [10, 20, 30]); //this指向ypf2,打印结果为60
}

// 案例3. bind绑定
{
  console.log("--------------------案例3------------------------");
  foo.bind("ypf1"); //仅绑定了,没有调用,没有输出
  let newFoo = foo.bind("ypf2");

  //下面属于 默认绑定和显式绑定冲突,优先级:【显式绑定】
  newFoo(); //this指向ypf2
}
View Code

5. 规则4-new绑定 

(this = new 创建出来的对象)

  JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。 使用new关键字来调用函数是,会执行如下的操作:

  a.创建一个全新的对象;

  b.这个新对象会被执行prototype连接;

  c.这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);

  d.如果函数没有返回其他对象,表达式会返回这个新对象;

即new操作符做了三件事:

var person={};
person.__proto__=Person.prototype;
Person.call(person)
function Person(name, age) {
  this.name = name;
  this.age = age;
}

var p1 = new Person("ypf", 18);
console.log(p1.name, p2.name);
View Code

 

二. 优先级总结

    1. 默认绑定的优先级最低:当存在其它规则的时候,会通过其它规则来绑定this

    2. 显式绑定优先级高于隐式绑定

    3. new绑定到优先级高于隐式绑定

    4. new绑定到优先级高于显式绑定中的bind (但是new绑定不允许和call、apply同时使用)

  总结: new绑定 > 显示绑定(apply/call/bind) > 隐式绑定(obj.foo()) > 默认绑定(独立函数调用)

 代码分享:

// 案例1
/* 
    1. call、apply的显式绑定高于隐式绑定
    2. bind优先级高于隐式绑定
*/
{
  console.log("------------------案例1---------------------");
  let obj = {
    name: "ypf",
    foo: function () {
      console.log(this);
    },
  };
  //call、apply的显式绑定高于隐式绑定
  obj.foo.call("ypf1"); //this指向ypf1
  obj.foo.apply("ypf2"); //this指向ypf2
  //bind优先级高于隐式绑定
  let test = obj.foo.bind("ypf3");
  test(); //this指向ypf3
}
// 更明显的比较
{
  function foo() {
    console.log(this);
  }
  let obj = {
    name: "ypf",
    foo: foo.bind("ypf6"),
  };
  obj.foo(); //this指向ypf6
}

// 案例2
/* 
    new绑定的优先级高于隐式绑定
*/
{
  console.log("------------------案例2---------------------");
  let obj = {
    name: "ypf7",
    foo: function () {
      console.log(this);
    },
  };
  var f = new obj.foo(); //返回的是foo函数对象
}

// 案例3
/* 
    new的优先级高于bind
*/
{
  console.log("------------------案例3---------------------");
  function foo() {
    console.log(this);
  }
  let bar = foo.bind("aaaa");
  let obj = new bar(); //this指向foo函数对象
}

 

三. 特殊情况总结

1. 一些函数中的this分析

(1). setTimeout

/* 
    this指向window
*/

setTimeout(function () {
  console.log(this); //指向window
}, 1000);
View Code

(2). 一些数组中的方法

{
  var names = ["abc", "cba", "nba"];
  names.forEach(function (item) {
    console.log(item, this); //this是window
  });
  names.forEach(function (item) {
    console.log(item, this); //this就是abc了
  }, "abc");
}
View Code

(3). 点击监听

{
  const boxDiv = document.querySelector(".box");
  boxDiv.onclick = function () {
    console.log(this);
    // 这里的this是boxDiv对象,函数内部相当于 boxDiv.click(),相当于隐士绑定
  };
}
View Code 

2. 忽略显式绑定

 apply/call/bind: 当传入null/undefined时, 自动将this绑定成全局对象。
{
  console.log("----------------案例1-------------------");
  function foo() {
    console.log(this);
  }
  foo.apply("abc"); // 典型的显式绑定,this指向abc
  foo.apply(null); //this指向全局对象window
  foo.call(undefined); //this指向全局对象window
  let bar = foo.bind(null); //this指向全局对象window
  bar();
}

3. 间接函数引用

 this指向window
{
  console.log("----------------案例2-------------------");
  let obj1 = {
    name: "obj1",
    foo: function () {
      console.log(this);
    },
  };
  let obj2 = {
    name: "obj2",
  };
  (obj2.bar = obj1.foo)(); //this指向window
}

4. 箭头函数的引用

 不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。
// 补充一个箭头函数的简写方式
// 如果一个箭头函数, 只有一行代码, 并且返回一个对象,返回的对象需要加个()
{
  var bar1 = () => {
    return { name: "why", age: 18 };
  };
  var bar2 = () => ({ name: "why", age: 18 });
}

// 案例3
{
  console.log("----------------案例3-------------------");
  let foo = () => {
    console.log(this);
  };
  foo(); //this指向一个空对象 {}
  foo.call("abc"); //this指向一个空对象 {}
  let obj = {
    name: "ypf",
    foo: foo,
  };
  obj.foo(); //this指向一个空对象 {}
}
// 箭头函数中this的实际应用
{
  var obj = {
    data: [],
    getData: function () {
      // 发送网络请求, 将结果放到上面data属性中

      // 在箭头函数之前的解决方案
      // var _this = this
      // setTimeout(function() {
      //   var result = ["abc", "cba", "nba"]
      //   _this.data = result
      // }, 2000);

      // 箭头函数之后(不绑定this,就去外层作用域(也就是getData中的this),也就是obj)
      setTimeout(() => {
        var result = ["abc", "cba", "nba"];
        this.data = result;
      }, 2000);
    },
  };

  obj.getData();
}
View Code

 

四. 面试题剖析

基础面试题参考:https://www.cnblogs.com/yaopengfei/p/16498086.html

1. 面试题1

 {
  console.log("------------------------面试题1-----------------------------");
  var name = "ypf"; //相当于给windows对象中加了个name属性
  var person = {
    name: "person",
    sayName: function () {
      console.log(this.name);
    },
  };

  function sayName() {
    var sss = person.sayName;
    sss(); //this指向window,this.name为ypf(默认绑定)
    person.sayName(); //this指向person (隐式绑定)
    person.sayName(); // (person.sayName)();   this指向person (隐式绑定)
    (b = person.sayName)(); //this指向window,this.name为ypf 赋值表达式(独立函数调用)
  }
  sayName();
}

2. 面试题2

{
  console.log("------------------------面试题2-----------------------------");
  var name = "ypf"; //相当于给windows对象中加了个name属性
  var person1 = {
    name: "person11",
    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: "person22" };

  person1.foo1(); //this指向person1,this.name等于person11  (隐式绑定)
  person1.foo1.call(person2); //this指向person2 ,this.name等于person22  (显式绑定优先级高于隐式绑定)

  person1.foo2(); //箭头函数this指向外层作用域就是window,this.name等于ypf
  person1.foo2.call(person2); //this指向window,this.name等于ypf  (error过)

  person1.foo3()(); //独立函数调用,this指向window,this.name等于ypf
  person1.foo3.call(person2)(); //独立函数调用,this指向window,this.name等于ypf
  person1.foo3().call(person2); //call优先级高于隐式绑定,this指向person2,this.name等于person22

  person1.foo4()(); //箭头函数不绑定this,指向外层作用域person1, 所以this.name等于'person11'    (error过)
  person1.foo4.call(person2)(); //箭头函数不绑定this,外层作用域this被显式绑定了person2,所以this.name等于 person22  (error过)
  person1.foo4().call(person2); // 箭头函数不绑定this,指向外层作用域person1, 所以this.name等于'person11'  (error过)
}

3. 面试题3

{
  console.log("------------------------面试题3-----------------------------");
  var name = "ypf"; //相当于给windows对象中加了个name属性
  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("person11");
  var person2 = new Person("person22");

  person1.foo1(); //person11 (new的优先级高)
  person1.foo1.call(person2); //person22 (显式高于隐式)

  person1.foo2(); //person11 (箭头函数,this指向上层作用域 person11)
  person1.foo2.call(person2); //person11   (箭头函数,this指向上层作用域 person11)

  person1.foo3()(); //ypf  (独立函数调用,this指向window)
  person1.foo3.call(person2)(); //window
  person1.foo3().call(person2); //person22

  person1.foo4()(); //person11
  person1.foo4.call(person2)(); //person22
  person1.foo4().call(person2); //person11
}

4. 面试题4 

{
  console.log("------------------------面试题4-----------------------------");
  var name = "ypf"; //相当于给windows对象中加了个name属性

  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("person11");
  var person2 = new Person("person22");

  person1.obj.foo1()(); //ypf (独立函数调用)
  person1.obj.foo1.call(person2)(); // ypf
  person1.obj.foo1().call(person2); //person22

  person1.obj.foo2()(); // obj
  person1.obj.foo2.call(person2)(); //person22
  person1.obj.foo2().call(person2); // obj
}

 

 

 

 

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2022-02-10 18:03  Yaopengfei  阅读(182)  评论(2编辑  收藏  举报