JavaScript 面试题外的【this】

我心飞翔 分类:javascript

对于 this ,是前端基础面试题中的常客,同时在哪都有汗牛充栋的的文章一一列举 this 在不同的上下文环境中的指向的文章。但背会面试题却并不能应付实际的开发。

假如在 js 中去掉 this ,会怎么样?

  • 全局函数 -> 直接写 window 嘛,干嘛用 this
  • 箭头函数 -> 在下内部本就没有 this,还是按照闭包的规则,该是谁就是谁
  • bindapplycall -> 在下本就是针对 this 的元编程,自然也不再需要,别了您内
  • 构造方法,对象方法 -> (尴尬)

其实 js 中的 this ,设计的目的便是实现看起来像 java 的 oop 模式。

const a = new foo()
a.bar() 
 

上述代码中,作为构造函数的 foothis 应该指向一个新的对象并作为结果返回,而作为方法的 bar 应该访问的是其对应的实例对象 a

让我们再瞟一眼声明

function foo() {console.log(this)}

function bar() {console.log(this)}

// 在某不知名的角落里
foo.prototype.bar = bar
 

在声明的时候我哪知道谁是构造函数,谁是实例方法(掀桌)。

好吧,那只能看函数执行时动态确定了,用 new [fn](...args) 执行的是构造函数,用 obj.[fn](...args) 执行的是对象方法。以此确定 this 的指向。

如此看来在函数中,本就不该访问 this,应该报错。在拥有闭包作用域的 lambda 表达式中,this 应该按闭包的规则向上层查找。

慢着,js 的函数本身就带闭包作用域那咋办?

啊啊啊,不管了, 直接就指向 window 吧,本就一周设计出来的玩意,还想咋地,又不是不能用……


js 相比其他很多语言拥有更高度的动态性,而这种动态性并非都是优势,一些来自于过于敷衍的设计的动态性,往往会造成各种灾难,this 便是一例。

this 这种灾难来自于设计者想把全局函数、lambda 表达式、构造函数、对象上的方法糅合在一起。然而这并无必要。

为什么其他语言不会有这种问题,因为其他语言把全局函数、lambda 表达式、构造函数、对象上的方法声明时分的很清楚,并不需要动态去确定。

以 rust 举例

pub struct ClassName {
    field: i32,
}

impl ClassName {
    // 充当构造函数的静态方法 new,返回一个 ClassName 实例
    pub fn new(value: i32) -> ClassName {
        ClassName {
            field: value
        }
    }

    pub fn public_method(&self) {
        // 方法内显示声明的 self 即是 js 内部隐式声明的 this
        println!("from public method");
        self.private_method();
    }

    fn private_method(&self) {
        println!("from private method");
    }
}
 

而 scala 的设计显然更为精妙。

// class 的声明代表这是一个构造函数
class Point(xc: Int, yc: Int) {
   // 构造函数中声明的变量/常量就是对象的属性
   var x: Int = xc
   var y: Int = yc

   // 构造函数中声明的函数就是对象的方法
   def move(dx: Int, dy: Int) {
      x = x + dx
      y = y + dy
      println ("x 的坐标点: " + x);
      println ("y 的坐标点: " + y);
   }
}

 

今时今日的 js 已经不再是那个 js 了。在 this 问题上,TC39 委员会又做了那些努力呢。

首先,箭头函数履行了 function 中 lambda 表达式的职能。在箭头函数中访问 this,也会以闭包的方式逐层向上查找了。var _this = this 已经成为了历(li)史(shi)。

全局函数中的 this,其实在严格模式中已经解决了一部分。

而真正应该用到 this 的地方即构造方法和实例方法,全都在 class 声明的括号内部。

也就说一般情况下,在我们的代码中, this 只能出现在 class 的括号内。

class Point {
  constructor(x, y) {
    // 显示声明的构造函数
    this.x = x;
    this.y = y;
  }

  toString() {
    // 显示声明的对象方法
    return '(' + this.x + ', ' + this.y + ')';
  }
}
 

为啥整篇文章避而不谈 call/apply/bind。因为在我看来这仨都属于 js 元编程的范畴。

何谓元编程?

元编程,即编写的程序可以生成、操纵其它程序,又或是在程序运行时改变其自身。

简而言之,就是在代码层面上赋予新的解释,针对代码进行编程。可以是由原来的代码生成新的代码,也可以是改写原代码的功能。

例如 proxy 就是让用户改写对象上的操作,call/apply/bind 则是改写函数内部的动态作用域规则(由于箭头函数内没有动态作用域,所以对它没用), with那更是厉害到让人闻风丧胆。

而元编程则是业务代码中不允许出现,框架代码中慎之又慎的东西。

我希望在你用到的时候已经不需要这篇文章了。


时下,不少人选择以面向面试题学习作为自己的学习方式。然而实际上应试教育和实际开发是有比较大的出入的。

譬如 this ,是前端基础面试题中的常客,同时在哪都有汗牛充栋的的文章一一列举 this 在不同的上下文环境中的指向的文章。有些文章甚至想出各种“花式”技巧,什么就近原则啊,什么各种嵌套啊。有用吗?私以为并没有啥用。反倒是那些能真的讲讲 this 为什么如此设计,以及如何应用的文章寥寥。

下一篇,打算聊聊绝大多数人都没在代码里写过的 WeakMap WeakSet,如果大家觉得本文还有点内容,还请高抬贵手点个赞哈。

回复

我来回复
  • 暂无回复内容