1、引言
想要理解this,你可以先记住以下两点:
- this永远指向一个对象;
- this的指向完全取决于函数调用的位置
在JavaScript语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象下运行,而this就是函数运行时所在的执行上下文(对象),所以this的指向是动态的。JS 中函数调用主要有以下几种模式:
不同的函数调用方式会有不同的上下文,理解this的关键就是:对应的是 哪一种函数调用及其 如何影响上下文。 下面将分别对各种函数调用方式分析this的指向。
2、函数调用
2.1 什么是函数调用
函数调用=函数名(参数),比如add(1,2),函数调用表达式不能是属性方式的调用。我们来看看以下两种函数调用方式,有一个直观的认识
1、 最简单的函数调用demo
function add(a, b) {
return a + b;
}
//函数调用
const result = add(1,2)
console.log(result); // => 3
1、 立即调用的函数表达式(IIFE)
const result = (function add(a, b) {
return a + b;
})(1,2)//函数调用
console.log(result); // => 3
2.2 函数调用中的this
在函数调用中,执行上下文是全局对象。在浏览器中(本文默认代码最终在浏览器运行),this是 window 对象。
- 看个demo
在函数作用域内调用
function add(a, b) {
console.log(this === window); // => true
this.number = 20; // 将'number'属性添加到全局对象
return a + b;
}
add(1, 2); // => 3
console.log(window.number); // => 20
在函数作用域外调用
console.log(this === window); // => true
this.testStr = 'Hello World!';
console.log(window.testStr); // => 'Hello World!'
2.3 严格模式下的函数调用 this
使用’use strict’的时候,this为undefined
- 看个demo
function add(a, b) {
'use strict'; // 启用严格模式
console.log(this === undefined); // => true
return a * b;
}
add(2, 5); // => 10
因此单个JS文件可能包含严格和非严格模式
2.4 总结
当函数独立调用的时候,在严格模式下它的this指向undefined,在非严格模式下,当this指向undefined的时候,自动指向全局对象(浏览器中就是window)
2、 方法调用
2.1 什么是方法调用
简单来说就是对象中的某个方法,我们通过对象来调用某个方法时就是方法调用,方法调用需要一个属性访问器形式来调用函数。
- 看几个简单的方法调用demo
const calculate = {
add: function(){
console.log(this); //Object{value: 1, add: ƒ}
this.value += 1;
}
}
calculate.add(); // 方法调用
['Hello', 'World'].join(', '); // 方法调用
const otherFunction = calculate.add;
otherFunction(); // 函数调用
otherFunction需要特别注意,这种形式的调用是属于函数调用,不要弄错了,这种情况属于方法和对象分离,我们具体来看看
- 方法与对象分离
方法可以从对象中提取到一个单独的变量const otherFunction = calculate.add,当方法单独调用时,与原始对象分离,此时的this指向全局对象window
function calculate() {
this.add = function() {
console.log(this === calculate); // => false
console.log(this) // => [object Window]
}
}
const cal = new calculate();
setTimeout(cal.add, 1000);
setTimeout(cal.add, 1000);等价于以下代码
const add = cal.add;
setTimout(add);
- 解决方案
- bind():方法与对象绑定cal.add.bind(cal)
- add改为箭头函数
2.2 方法调用中 this 是肿么样
可以看出this指向调用者,下面看几个实际的demo
const calculate = {
num: 0,
add: function() {
console.log(this === calculate); // => true
this.num += 1;
return this.num;
}
};
calculate.add(); // => 1
calculate.add(); // => 2
3、构造函数调用
3.1 什么是构造函数调用
当new关键词紧接着函数对象,执行的是构造函数调用,比如new calculate()
es6之后我们可以用class关键词结合constructor来定义构造函数,当然也可以使用function来定义构造函数
- function构造函数
function calculate(){
this.x = '1';
this.y = function(){};
}
var cal = new calculate();//构造函数调用
- class构造函数
class calculate {
constructor(number1, number2) {
this.number1 = number1;
this.number2 = number2;
}
}
var cal = new calculate(1,2);//构造函数调用
3.2 构造函数中的 this
new calculate() 正在进行构造函数调用,其中上下文是cal。 在calculate内部初始化对象:this.property被赋值为默认值。
class calculate {
constructor() {
console.log(this instanceof calculate); // => true
this.property = 'Default Value';
}
}
const cal = new calculate();
cal.property; // => 'Default Value'
3.3 new做了些啥
如果函数作为构造函数掉用,那么其中的this就代表它即将new出来的对象,主要有几个步骤
- 创建一个临时对象
- 给临时对象绑定原型
- 给临时对象对应属性赋值
- 将临时对象return
4、 隐式调用
4.1 什么是隐式调用
使用calculte.call()或calculte.apply()方法调用函数时,执行的是隐式调用
- call和apply区别
- 本质上讲都是动态的改变this上下文,
- 第一个参数都是,指定函数体内this的指向
- 第二个参数开始不同,apply是数组或者类数组
- call比apply的性能要好,平常可以多用call
方法 .call(thisArg[, arg1[, arg2[, ...]]])将接受的第一个参数thisArg作为调用时的上下文,arg1, arg2, ...这些则作为参数传入被调用的函数。
方法.apply(thisArg, [args])将接受的第一个参数thisArg作为调用时的上下文,并且接受另一个类似数组的对象[arg1, arg2, ...]作为被调用函数的参数传入
4.2 隐式调用中的this
下面看下实际的列子
const obj = {name: "test"}
function calculate(){
console.log(this === obj); // => true
return this.name;
}
console.log(calculate.call(obj))// => "test"
5、绑定函数bind
5.1 什么是绑定函数
与.apply()和.call() 方法不同,它不会立即调用该函数,.bind()方法只返回一个新函数,在之后被调用,只是this已经被提前设置好了
bind语法: func.bind(thisArg[, arg1[, arg2[, …]]])
1、thisArg 当绑定函数被调用时,该参数会作为原函数运行时的this指向。当使用 new 操作符调用绑定函数时,该参数无效。
2、arg1, arg2, … 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。
- 使用方式大概如下:
const newFn = fn.bind(thisObj);
newFn(arg1, arg2...)
5.2 bind中this的指向
- demo
var obj = {};
function calculate() {
console.log(this === obj);
}
var testObj = calculate.bind(obj);
testObj(); //true
.bind()创建一个永久的上下文链接,并始终保持它。 一个绑定函数不能通过.call()或者.apply()来改变它的上下文
6、箭头函数
在了解箭头函数之前,我们先回顾下常规函数的写法
function test(name) { //声明式写法
console.log(name)
}
let test2 = function(name) { //赋值式写法
console.log(name)
}
6.1 什么是箭头函数
ES6新增了箭头函数,用于以更短的形式声明函数,并在词法上绑定上下文,但是箭头函数有几个规则
- 规则一:箭头函数只能用赋值式写法,不能用声明式写法
- 规则二:如果参数只有一个,可以不加括号,如果没有参数或者参数多于一个就需要加括号
const test = name => {
console.log(name)
}
const test2 = (name1, name2) => {
console.log(name1 + ' and ' + name2)
}
- 规则三:如果函数体只有一句话,可以不加花括号
const test = name => console.log(name)
- 规则四:如果函数体没有括号,可以不写return,箭头函数会帮你return
const test = (p1, p2) => p1 + p2
- 规则五:箭头函数不能用作构造函数
6.2 箭头函数中的this
箭头函数不会创建自己的执行上下文,而是从定义它的外部函数中获取 this。 换句话说,箭头函数由外部来决定this的指向。
来看几个demo
- 箭头函数在函数作用域时的this
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
log() {
console.log(this === myPoint); // => true
setTimeout(()=> {
console.log(this === myPoint); // => true
console.log(this.x + ':' + this.y); // => '95:165'
}, 1000);
}
}
const myPoint = new Point(95, 165);
myPoint.log();
- 箭头函数在顶层作用域时的this
以下函数在顶层作用域,this指向的是window
const getContext = () => {
console.log(this === window); // => true
console.log(this); // =>[object Window]
};
getContext()
7、 问题思考和解答
7.1 函数嵌套时this的指向
const calculate = {
number1: 5,
number2: 10,
sum: function() {
console.log(this === calculate); // => true
function add() {
console.log(this === calculate); // => false
return this.number1 + this.number2;
}
return add();
}
};
calculate.sum();
- ❓思考:上面add方法中this的指向是什么?
内部函数的上下文只依赖于它的调用类型,而不依赖于外部函数的上下文,add()是一个函数调用,它将this作为全局对象window(非严格模下)。所以this指向window
8、总结
之前对javascript一知半解,现在重新来学习,这是写的第一篇前端文章,有问题欢迎指出
参考文章: