ReactNative中的this引发的思考

this是什么?我们在实践ReactNative的过程中,这个不起眼的问题,开始只知道用bind绑定或箭头函数来解决。对于前端门外汉来说有必要研究一下,结果一看,发现问题比想象的还要复杂,js语法比较奔放,没有严格的对象的概念。外在表现上:1.看似相似的代码,输出结果相差很大。2.同一段代码,不同环境,不同状态,严格模式,非严格模式,输出结果相差很大。

首先看看在各种全局环境中直接打印console.log(this)的情况吧


1.浏览器环境中,控制台打印(图1),可以看出this指向浏览器中的全局变量Window

图1.png

2.node的repl环境中,执行结果(图2),this指向node环境中的全局变量global对象

(图2).png

3.node执行js文件中,执行如下代码

console.log(this)
console.log(global)

控制台输出结果是什么呢?

图3.png

也就是在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):


图3.png

以上说明不同环境下,虽然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对象

image.png

原型链中也是一样,下面的例子,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的描述

你可能感兴趣的:(ReactNative中的this引发的思考)