函数调用模式的区别-this
前言
学习js
也快有一段时间了,一直不怎么想碰this
,也碰到别人写的一些关于this
,一直懒得看,但是最近在学习DOM事件,写代码中经常看见this
,有时候理解的不太透彻,所以决定把它研究一下,当然了,现在写的this
也只是我现在的水平和参考一下大神的文章,自己结合起来,写给自己的
正文:
引自 MDN this
- 与其他语言相比,函数的 this关键字在JavaScript中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。
- 在绝大多数情况下,函数的调用方式决定了this的值。this不能在执行期间被赋值,并且在每次函数被调用时this的值也可能会不同。ES5引入了bind方法来设置函数的this值,而不用考虑函数如何被调用的,ES2015引入了支持this词法解析的箭头函数(它在闭合的执行上下文内设置this
的值)。
什么是this?
this
是在执行上下文创建时确定的一个在执行过程中不可更改的变量。
- 所谓执行上下文,就是JavaScript引擎在执行一段代码之前将代码内部会用到的一些变量、函数、this提前声明然后保存在变量对象中的过程。这个’代码片段’包括:全局代码(script标签内部的代码)、函数内部代码、eval内部代码。而我们所熟知的作用域链也会在保存在这里,以一个类数组的形式存储在对应函数的[[Scopes]]属性中。
正文说:严格模式与飞非严格模式会有区别,我们来看一下:
看一下严格模式下
var a = 1;
function fun() {
'use strict';
var a = 2;
return this.a;
}
fun(); //报错: Cannot read property 'a' of undefined
非严格模式下:
var a = 1;
function fun() {
var a = 2;
return this.a;
}
fun(); // 输出 1
总结: 当函数独立调用的时候,在严格模式下它的this指向undefined,
在非严格模式下,当this指向undefined的时候,自动指向全局对象(浏览器中就是window)
正文说:绝大多数情况下,函数的调用方式决定了this的值
我们来看看函数有几种调用方式
-
函数调用模式:
函数名()
- 方法调用模式
-
构造函数调用模式
new Function(...)
-
apply(call)
调用模式
函数调用模式
//例子:
function add(i,j){
console.log(this); //Window
// console.log(arguments);
var sum = i+j;
console.log(sum);
(function(){
console.log(this); //Window
})()
return sum;
}
add(1,2);
从上面的例子我们看到this
两次返回的都是window
全局对象,匿名函数也会产生本地作用域,打印也是window
按理说第一次打印应该是undefined
,但是浏览器里有一条规则:
如果你传的 context 就 null 或者 undefined,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined)
总结:函数调用模式this指向window的全局对象
方法调用模式
//例子:
var myNumber = {
value: 1,
add: function(i){
console.log(this); //Object{value: 1, add: ƒ}
this.value += i;
}
}
myNumber.add(1);
函数实现改变value的值加1,add被调用时,this
的指向是Object对象
总结:方法调用模式this指向调用者
构造函数调用模式
//例子:
function Car(type,color){
this.type = type;
this.color = color;
this.status = "stop";
this.light = "off";
console.log(this); //打印结果是我们创建的对象
}
Car.prototype.start = function(){
this.status = "driving";
this.light = "on";
console.log(this.type + " is " + this.status);
}
Car.prototype.stop = function(){
this.status = "stop";
this.light = "off";
console.log(this.type + " is " + this.status);
}
var benz = new Car("benz", "black"); //Car {type: "benz", color: "black", status: "stop", light: "off"}
如果函数作为构造函数用,那么其中的this就代表它即将new出来的对象
。为啥呢?new做了啥呢?new做了下面这些事:
- 创建一个临时对象
- 给临时对象绑定原型
- 给临时对象对应属性赋值
- 将临时对象return
也就是说new其实就是个语法糖,this之所以指向临时对象还是没逃脱上面说的几种情况。
总结:构造函数调用模式this指向被构造的对象
apply(call)调用模式
- this指向第一个参数
对于这一个,由于自己还没学到,先借鉴MDN上的,后面自己再总结一下
如果要想把this的值从一个context传到另一个,就要用call,或者apply
方法。
// 一个对象可以作为call和apply的第一个参数,并且this会被绑定到这个对象。
var obj = {a: 'Custom'};
// 这个属性是在global对象定义的。
var a = 'Global';
function whatsThis(arg) {
return this.a; // this的值取决于函数的调用方式
}
whatsThis(); // 直接调用, 返回'Global'
whatsThis.call(obj); // 通过call调用, 返回'Custom'
whatsThis.apply(obj); // 通过apply调用 ,返回'Custom'
当一个函数的函数体中使用了this关键字时,通过call()方法和apply()方法调用,this的值可以绑定到一个指定的对象上。call()和apply()的所有函数都继承自Function.prototype 。
function add(c, d) {
return this.a + this.b + c + d;
}
var o = {a: 1, b: 3};
// 第一个参数是作为‘this’使用的对象
// 后续参数作为参数传递给函数调用
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
// 第一个参数也是作为‘this’使用的对象
// 第二个参数是一个数组,数组里的元素用作函数调用中的参数
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
使用 call 和 apply 函数的时候要注意,如果传递的 this 值不是一个对象,JavaScript 将会尝试使用内部 ToObject 操作将其转换为对象。因此,如果传递的值是一个原始值比如 7 或 'foo' ,那么就会使用相关构造函数将它转换为对象,所以原始值 7 通过new Number(7)被转换为对象,而字符串'foo'使用 new String('foo') 转化为对象,例如:
function bar() {
console.log(Object.prototype.toString.call(this));
}
//原始值 7 被隐式转换为对象
bar.call(7); // [object Number]
参考资料