第四节:JS中的this指向详解(规则总结、优先级分析、特殊情况)和面试题剖析
一. 四大规则总结
1. 背景说明
(1). 为什么需要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();
(2). this的全局指向?
/* 在大多数情况下,this都是出现在函数中的。 在全局作用域下: 1. 浏览器中运行,this是window 2. node环境中,this是空对象 {} */ console.log(this);
(3). 抛砖引玉
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
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 (说明只与调用位置有关) }
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就指向哪个对象 }
4. 规则3-apply/call/bind显式绑定
// 前置函数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 }
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);
二. 优先级总结
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);
(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"); }
(3). 点击监听
{ const boxDiv = document.querySelector(".box"); boxDiv.onclick = function () { console.log(this); // 这里的this是boxDiv对象,函数内部相当于 boxDiv.click(),相当于隐士绑定 }; }
2. 忽略显式绑定
{ 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. 间接函数引用
{ console.log("----------------案例2-------------------"); let obj1 = { name: "obj1", foo: function () { console.log(this); }, }; let obj2 = { name: "obj2", }; (obj2.bar = obj1.foo)(); //this指向window }
4. 箭头函数的引用
// 补充一个箭头函数的简写方式 // 如果一个箭头函数, 只有一行代码, 并且返回一个对象,返回的对象需要加个() { 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(); }
四. 面试题剖析
基础面试题参考: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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。