JavaScript语言里有诸多“反人类”的设计,其中一个就是 this
关键字。今天整理一下最近学习的this
用法。
首先,为什么会有 this
这个用法?
JavaScript想要实现这样的理念:
代码执行的结果应该可以随着环境的变化而自动变化
所以就需要执行环境这一概念,某个函数在不同的条件下使用,所产生的结果可能是不同的,例如下面这段代码,
function fn(){
console.log(this.s);
}
s=“window”;
var obj = {
s:“obj”,
fn:fn,
}
var o = obj;
o.fn();
fn();
/**
* output:
* obj
* window
*/
o.fn
和 fn
都指向 function fn(){}
,但为什么结果会不一样呢?其实就是因为两者的执行环境不同.
-
o.fn
执行的时候,解释器先有o
的内存地址找到fn
的内存地址,所以o.fn()
的执行环境是o
,既然执行环境是o
,那么this
就是o
,this.s
就等于 "obj" -
fn
执行的时候,解释器直接从浏览器的全局栈内存下找到fn
的内存地址,所以fn()
的执行环境是window
,而window
的属性s
是和当前作用域的变量s
双向绑定的,所以this.s
就是window.s
就是 "window"
上述其实是this在 function 中的第一个应用,即 方法执行时会得到一个执行环境,执行环境是谁取决于方法前是否有点,有点this就是点前面的对象,没点就是浏览器的window
这条应用在实际环境中比如给某个元素的触发事件,当事件触发方法执行,方法中的this
是当前元素本身
下面是在 function 中的第二个应用,this
在构造函数执行时,会给创建的实例添加属性,例如以下代码:
function Fn(name, age) {
var n = 10;
this.name = name;
this.age = age + n;
}
var f1 = new Fn("xxx", 20);
var f2 = new Fn("xxx", 30);
console.log('name' in f1) // true
console.log(f1.n); // undefined
console.log('toString' in f1) // true
- name是有this绑定到实例上的属性,当执行
new
执行构造函数时,会默认开辟一块堆内存(实例),执行到this
语句时会把属性加入这个堆内存,执行完之后将堆内存的地址赋给等号前的变量。 -
n
是Fn
函数的私有变量,和实例没有关系 -
toString
是Object
的方法,通过原型链继承,f1
也可以得到这个方法,in
可以检测对象是否有这个属性或方法,无论是绑定在this还是原型链上继承来的,所以返回true
综上所述,this
在function
的应用可归纳成两点:
-
方法执行时会得到一个执行环境,执行环境是谁取决于方法前是否有点,有点this就是点前面的对象,没点就是浏览器的window
-
构造函数执行时,this会给创建的实例绑定属性
这么看来this
应用的时候会比较固定,但JavaScript也提供了三个方法通过人为方法改变this
(在之后的篇章会写到)
等下,为什么总是强调this在function中的应用,而不直接说在函数中的应用呢?
因为在ES6推出后,不仅function可以创建函数,箭头函数也可以创建,接下来就是一个神奇的坑了,
箭头函数没有this!!!
什么叫“没有this“?就是说箭头函数执行的时候,不存在一个系统创建好的this变量来实现上面的两条性质
window.name = 'WIN';
// this是当前函数的执行主体,但是箭头函数没有执行主体
// 因为fn所处的执行上下文是window,所以this是window
let fn = n => {
console.log(this.name);
}
let obj = {
name: 'OBJ',
fn:fn,
}
fn(10); // => this: window
obj.fn(10); // => this还是window,因为执行上下文是window
在上面的例子中,this
不会因为调用他的方法前面有没有点而变化,因为箭头函数执行的私有作用域中没有this
这个变量,所以在执行到含有this
的语句时,系统会去它的上级作用域里找,在这段代码中,fn
的上级作用域是全局作用域,所以每次执行得到的都是window
的name
所以,箭头函数尽量不要用this
,因为它和普通函数机制不同,但也不是完全没有应用,例如下面这个需求
需求:1s后把obj的name改成“lz”
箭头函数做法
window.name = 'WIN';
let obj = {
name: 'syf',
fn: function () {
setTimeout(() => {
console.log(this); // obj
this.name = "lz";
}, 1000)
}
}
obj.fn(); // this是obj
为什么不用function创建fn
函数?因为setTimeout
是window
的一个方法,所以在调用setTimeout
的时候this
总是window
,需求没法实现
而在使用箭头函数时,setTimeout
作用域中没有this
,所以要去上级作用域寻找,就是obj
作用域,然后就可以改obj.name
了
由this
引申出来的点有三个,之后再补充
- call|apply|bind 实现this的修改
- 用function创建类和用class创建类的对比
- 原型链机制
网友评论