this是什么?我们在实践ReactNative的过程中,这个不起眼的问题,开始只知道用bind绑定或箭头函数来解决。对于前端门外汉来说有必要研究一下,结果一看,发现问题比想象的还要复杂,js语法比较奔放,没有严格的对象的概念。外在表现上:1.看似相似的代码,输出结果相差很大。2.同一段代码,不同环境,不同状态,严格模式,非严格模式,输出结果相差很大。
首先看看在各种全局环境中直接打印console.log(this)
的情况吧
1.浏览器环境中,控制台打印(图1),可以看出this指向浏览器中的全局变量Window
2.node的repl环境中,执行结果(图2),this指向node环境中的全局变量global对象
3.node执行js文件中,执行如下代码
console.log(this)
console.log(global)
控制台输出结果是什么呢?
也就是在node文件中执行,this和gobal完全两码事。
此处拓展一下,node文件中js代码,遵循CommonJS规范,每一个文件是一个模块,大的方面讲模块化无非起到封装抽象的作用,便于重用与管理,小的方面可以防止变量相互污染危险,变量被封装特定的区域内,一个js文件或模块只需暴漏自己想暴漏的方法就好来,此时直接打印的this指向模块的module.exports,而不是全局的global。
为了验证这个想法,我们再执行一段代码,大家可以体会一下:
console.log('this:',this);
module.exports.foo = 5;
console.log('this:',this);
console.log(global);
执行结果如下(图4):
以上说明不同环境下,虽然this指向各异,但总体来说感觉就是指向当前的作用域的最大对象,浏览器中的window,node中的gobal,node文件中的exports,这更像一种约定俗成,this即默认绑定在全局变量,或者说当前作用域的最大对象上,暂时叫相对全局环境吧(自己YY的)。这也无怪乎,ECMAScript将要推出一个globalThis的变量。
再来个例子,大家先想一下,在浏览器模式下的输出:
function foo(){
console.log(this.a);
}
function doFoo(fn) {
fn();
}
var obj = {
a: 2,
foo: foo
};
var a = "oops,glbal";
doFoo(obj.foo);
重点来了
this到底是个啥?相对于OC中的self,Java中的this都是指向当前的对象,js中的this完全不同,可以理解为它指向运行时的上下文(上下文的属性),而不是指向对象自己,也不是指向函数所在的作用域。这一点很容易和js中的变量作用域搞混,js中变量的作用域是词法作用域,在编译器期基本确定了自己的作用域。而this是运行时确定,由调用位置决定的,两者有很大的差异。结合上面的例子,this所在的函数foo,貌似被obj绑定了,实际上在doFoo的参数上进行了赋值,调用位置在doFoo中的fn()处调用。属于this的默认绑定,和我们开始直接打印console.log(this)是一样的,运行时的上下文就是在相对全局环境中执行的,没有绑定到对象。
this的绑定:
默认绑定:
在浏览器环境下,非严格模式下,一个变量时被绑定到全局变量上的。当然公认不是好的设计,严格模式禁止自动或隐式创建全局变量,什么也不绑定,像没有赋值的变量一样,指向undefined
隐式绑定:
运行时态决定this的具体指向,即谁执行了对应的函数,函数中的this就指向谁
var obj = {
b: {
fn:function(){
console.log(this);
}
}
}
obj.b.fn();
var f = obj.b.fn;
f()
f()执行的是global,所以打印出的是global;
obj.b.fn()执行的是b,所以打印出的是b对象
原型链中也是一样,下面的例子,p的f属性继承自他的原型。但是调用时是p调用的,所以this指向了p
var o = {
f: function() {
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
显式绑定:
apply, call, bind 这三个方法是函数对象的函数原型Function.prototype的方法,所以所有任何函数都可以调用,通过改变函数的调用对象而改变this的指向,区别是apply, call会立刻执行,bind只是绑定当前不执行;apply和call主要是接受参数的格式不同。
function foo() {
console.log('id:', this.id);
}
foo(); //undefined
foo.call({ id: 42 }); //42
foo.apply({ id: 52 }); //52
var good = foo.bind({ id: 62 });
good()//62
new对象中this的绑定:
function Animal(name) {
this.name = name
}
let p = new Animal('Dog')
console.log(p.name)
new创建对象的伪代码如下:
new Animal('name') = {
var obj = {};//1
obj.__proto__ = Animal.prototype;//2
var result = Animal.call(obj,"name");//3
return typeof result ==='object'? result : obj;//4
}
new一个对象的步骤:
1创建一个空对象
2.空对象属性proto 指向对象的原型
3.绑定空对象与函数
4.return 这个对象
主要是第三步使用了显式绑定,保证了this指向新创建的对象。
需要注意的是在调用构造函数的过程中,手动的设置了返回对象,与this绑定的默认对象就会被丢弃了。
function C1(){
this.a = 37;
return {b:36};
}
c1 = new C1();
console.log(c1.a); // undefined
function C2(){
this.a = 37;
return {a:38};
}
c2 = new C2();
console.log(c2.a); // logs 38
function C3(){
this.a = 37;
return 38;
}
c3 = new C3();
console.log(c3.a); // logs 37
基本的判断步骤就是:1.是否是new中this 2.是否是显示绑定 3.是否是隐式绑定 4.最后是相当全局绑定
箭头函数中的this绑定:
1.适用于非方法函数(方法函数:对象属性中的函数就被称之为方法函数),如下面的例子,正常对象的方法,应该可以访问对象内的变量,实际不符合预期
x=11;
var obj={
x:22,
say:()=>{
console.log(this.x);
}
}
obj.say();//11,访问的是全局变量的x
2.不能用call方法修改里面的this
3.箭头函数没有this,所以需要通过查找作用域链来确定this的值,绑定的就是最近一层非箭头函数的this或全局变量,或者说与创建时封闭词法环境的this保持一致。
看下面的两个例子自己体会一下:
function foo() {
return () => {
return () => {
return () => {
console.log("id:", this.id);
};
};
};
}
var f = foo.call({id: 1});//内部的函数的this绑定在了foo()上了
var t1 = f.call({id: 2})()();//id:1
var t2 = f().call({id: 3})();//id:1
var t3 = f()().call({id: 4});//id:1
var obj = {
bar: function() {
var x = (() => this);
return x;
}
};
var fn = obj.bar();
var fn2 = obj.bar;
console.log(fn() === obj); // true
console.log(fn2()() == window); // true
ReactNative中,模块化遵循ES6的模块要求,主要是箭头函数需要注意,比较常见的是点击事件,绑定当前对象的常见做法:
1.bind绑定对象
class Home extends [React.Component](http://react.component/) {
constructor(props) {
super(props);
this.state = {
};
this.del = this.del.bind(this);//通过绑定,改变函数中的this指向
}
del(){
console.log('del')
}
render() {
return (
this is a text
);
}
2.onpress方法内使用箭头函数
class Home extends [React.Component](http://react.component/) {
constructor(props) {
super(props);
this.state = {
};
}
del(){
console.log('del')
}
render() {
return (
this.del()}>//通过箭头函数,将this拓展到home组件
this is a text
);
}
}
3.调用函数使用箭头函数
class Home extends [React.Component](http://react.component/) {
constructor(props) {
super(props);
this.state = {
};
}
//通过箭头函数,将this拓展到home组件
del = () => {
console.log('del')
}
render() {
return (
this is a text
);
}
}
总结:
以上列举的情况,还不够充分,有些具体问题还要具体分析,不过总体的原则基本上就是这些,现在基本解决了我平时遇到的this问题。经过此次多方测试与比较,对this有了更深刻的认识,或者说对js有了更深刻的认识,由于历史原因,我们看到js也是逐步向前发展的,方兴未艾的前端技术,一定会推动其向更好的方向发展。
执行环境:
chrome浏览器版本号:78.0.3904.108
node本地版本号:v12.4.0
ReactNative版本号:0.60.3
主要参考文献:
大家对于this的解读,也是很有热情,不断有文章会汇总讲解这个知识点,我也阅读了不少,我建议仔细阅读下面的书和文档,就可以解决这点上的问题。我参考的其他文章和例子侧重点各不相同就不单独列出了。
1.图书《了解不起的JavaScript 上卷》,对js的作用域/this/对象有清晰的讲解
2.MDN中关于this的描述