✏️ 一般说到Js的this,都会想起在函数中变来变去的this。但是事情的发生都是有规则的约束,Js中的this也不例外,下面我们来看一下JS中this指向的规则
✅ 在JS中,有四条规则影响着this的指向
- 默认指向:对于一个函数,如果没有被打点调用【x.xx】,则该函数里的
this指向全局【window】 - 隐式指向:对于一个函数,如果被打点调用,则该函数里的
this指向调用该函数的对象 - 显式指向:通过
call、apply、bind改变函数里的this指向指定的对象 - new关键字:构造函数的
this指向该实例化对象
如果上面4种有同时存在的情况,则那他们的优先级为: 4 > 3 > 2 > 1
一 、默认指向
对于一个函数,如果没有被打点调用【x.xx】,则该函数里的
this指向全局【window】
1) 🛴 先看一个30km/h的小例子
var age = 18;
function showAge () {
var age = 100;
console.log('my age is: ' + this.age);
}
showAge();
运行的结果为 my age is : 18
其实这也是符合默认指向这个规则的,在这个小例子中,showAge函数在执行的时候并没有被打点调用如:×××.showAge(),所以showAge函数里面的this指向全局window,即this.age就是全局的age为18。
可能有人会有疑问,为什么不是打印100呢。首先要清楚的一点是,在这个小例子中,打印的是this.age而不是age,所以要到this指向的对象上找age,而不是单纯地去找age这个变量,如果改成console.log('my age is: ' + age),打印的就是100了。
2) 🏍 再来一个60km/h的小例子
var age = 18;
var obj = {
age : 100,
showAge : function () {
setTimeout(function () {
console.log('my age is: ' + this.age);
}, 300);
return '请稍等300毫秒';
}
}
obj.showAge();
运行的结果为my age is : 18
嗯???为什么还是18???其实把函数执行的位置捋清楚也是符合规则的。
首先,showAge函数定义在一个对象中,里面有个定时器setTimeout,定时器等待300毫秒后执行打印操作,这应该没问题。
然后,在外面执行obj.showAge(),打点调用obj里的showAge方法,所以showAge里的this指向obj,但是,我们要操作的语句是在定时器setTimeout里面的函数里的,所以我们可以粗略地模拟一下setTimeout这个函数
function setTimeout (callback, delay) {
...
if (...) {
callback();
}
...
}
好了,大概就是这样子~~
我们可以看出,在定时器中,作为回调函数的callback只是单纯地在符合条件后执行了,那么,这里的callback函数是不是没有被谁打点调用,所以该函数的this应该指向全局对象window。所以打印的就是全局的age,等于18。
同时也可以得到一个普遍的结论:一般的回调函数的this指向全局对象window。【箭头函数除外】
二、隐式指向
对于一个函数,如果被打点调用,则该函数里的
this指向调用该函数的对象
1) 🛴 先看一个30kn/h的例子
var age = 18;
var obj = {
age : 100,
showAge : function () {
console.log('my age is: ' + this.age);
}
}
obj.showAge();
运行结果是my age is: 100
由obj.showAge()可以看出,函数showAge被obj这个对象调用,所以showAge函数里面的this 指向obj这个对象,所以你也可以把showAge函数的执行语句理解为console.log('my age is: ' + obj.age),所以打印的就是对象obj上的age,为100。
2) 🏍 再来一个60km/h的例子
var obj1 = {
age : 18,
showAge : function () {
console.log('my age is: ' + this.age);
}
}
var obj2 = {
age : 100,
showAge : function () {}
}
obj2.showAge = obj1.showAge;
obj2.showAge();
运行结果是my age is: 100
首先,执行obj2.showAge = obj1.showAge,把obj1的showAge函数的引用给obj2的showAge,所以obj2的showAge的执行内容就变成了obj1的showAge内容
然后执行obj2.showAge(),shoeAge的this指向obj2,所以打印的是obj2的age,为100。
三、显示指向
通过
call、apply、bind改变函数里的this指向指定的对象
先简单说一下call apply bind 的作用和使用方法
callapply都是改变一个函数的this指向并执行该函数,区别是传参列表不同bind是改变一个函数的this指向并返回一个新的函数,且只接收第一次绑定的this
1) 🛴 先来一个30km/h的例子
var obj = {
age : 100,
showAge : function () {
console.log('my age is: ' + this.age);
}
}
var newObj = {
age : 18
}
obj.showAge.call(newObj); //apply效果一样
//var func = obj.showAge.bind(newObj); //拿个变量接收bind方法执行后返回的新函数
//func(); //结果和call apply相同
运行结果是:my age is: 18
首先,执行语句从左往右看,调用obj对象中的showAge方法,然后使用call方法改变showAge方法中的this为newObj对象。所以,showAge函数里的执行语句可以理解为console.log('my age is: ' + newObj.age),所以打印的是newObj里的age,为18。
2) 🚗 剖析一下call改变this的原理,车速100km/h
首先,在隐式指向中我们知道,对于一个函数,如果被打点调用,则该函数里的this指向调用该函数的对象,那么我们就可以利用这点来模拟一下call方法
Function.prototype.myCall = function (context) {
var ctx = null;
//保证不污染原对象
if (context) {
ctx = JSON.parse(JSON.stringify(context));
}else {
ctx = window;
}
var arg = arguments, //保存函数的实参列表
len = arg.length,
args = [];
ctx.fn = this;
for (var i = 1; i < len; i++) {
// 从实参列表的第一位开始遍历,因为第零位为指向的上下文
args.push('arg[' + i + ']');
}
//遍历完后 args = [ 'arg[1]', 'arg[2]', ...]
var result = eval( 'ctx.fn(' + args.join(',') + ')' );
//上述语句可理解为 eval('ctx.fn(arg[1], arg[2], ...)')
delete ctx.fn;
return result;
}
运行结果依旧为my age is: 18
首先,从结果看,myCall函数没有问题,然后我们来分析一下函数里面到底主要干了些啥
-
通过
ctx.fn = this把调用myCall的函数变为目标上下文的一个方法,由此可以改变fn方法的this为目标上下文,也就是调用myCall的函数的this指向了目标上下文 -
把调用
myCall的函数的参数整合,通过eval方法把字符串解析为脚本并执行
四、通过new实例化一个对象
构造函数的
this指向该实例化对象
1) 🛴 先来一个30km/h的例子
function Person (name, age) {
this.name = name;
this.age = age;
this.introduceYourself = function () {
console.log('my name is ' + this.name, 'and I\'m ' + this.age + ' years old.');
}
}
var person = new Person('xuyede', 18);
person.introduceYourself();
运行结果为my name is xuyede and I'm 18 years old.
为了更好地理解,可以把new Person()的过程分成4个步骤理解
- 在
Person函数里面的第一行隐式生成一个对象var this = {} - 让该
this的原型指向该函数的原型this.__proto__ = Person.prototype - 执行
Person函数 - 在函数最后一行把这个
this返回出去
所以,可以理解为变量person拿到了构造函数里面this的引用,所以person可以使用在构造函数里设置在this上的值。
2) 🏎 看一下new这个东西到底干了什么,车速180km/h
function myNew() {
var func = [].shift.call(arguments); //把实参列表的第一位截取出来,获得构造函数
var instance = Object.create(func.prototype); //声明一个上下文,原型为构造函数的原型
func.apply(instance, arguments); //执行该构造函数,并改变构造函数的this为instance
return instance; //返回这个上下文
}