分类
js

使用 Proxy 创建有魔术属性/方法的类

前几天在 SF 回答了这个问题:如何使用proxy,如何在内部拦截get方法,然后翻了翻以前写的博客:使用 Proxy 添加魔术属性/方法,发现上次写完代理对象就停笔了,所以今天补全一下:创建有魔术属性/方法的类。这样就比较完整了。

JavaScript 构造函数的特点

ES6 增加了 class 关键字,梳理了面向对象的语法,现在我们可以这样定义一个类:

class Person {
  constructor(name) {
    this.name = name; 
  }

  hello() {
    return `Hello, my name is ${this.name}.`;
  }
}

如果你有其它面向对象语言的经验,应该很容易理解这段代码。

不过 JS 的构造函数特别,它支持 return 一个其它对象,作为 new SomeClass() 的结果。(如果不 return 或者 return 一个空对象,那么 new SomeClass() 得到的就是 SomeClass 的实例。)

也就是说,原则上,我们可以这么做:

class Person {
  constructor(name) {
    this.name = name;
    return {name: 'Meathill'};
  }
}

const person = new Person('张三');
console.log(person.name); // 'Meathill'

结合 Proxy

理解了上一节的内容,我们就很容易得到这样一个类:

class Person {
  constructor(name) {
    this.name = name;

    return new Proxy(this, {
      get(target, property) {
        if (property in target) {
          return target[property];
        }
        console.warn('Sorry, I can't do that.');
      }
    }
  }
}

在这个类的构造函数里,我返回了一个 Proxy 实例,代理了对真正 Person 实例的访问。当访问的属性/方法在实例上时,就返回需要的属性/方法,否则的话,输出警告。

实际上,Proxy 的 getset 的功能远不止如此,上面的代码只是一些演示。

用途

魔法属性/方法主要有以下用途:

  1. 对象 a 要使用一部分对象 b 的功能,但是又不方便直接用原型链。比如上一篇文章的场景,我提供类 VElement 作为接口,实际完成工作的是另一个沙箱中的 Element。
  2. 不知道会怎么访问对象,希望所有访问都照顾到。
  3. 希望捕获到对对象的修改,也就是 Vue 3.0 的核心修改。

Vue 3.0

Vue 1.x & 2.x 期间,都在使用 ES5 的 Object.defineProperty 拦截对对象的修改,实现响应式。这样的做法看起来很神奇,给 Vue 带来了巨大的成功。但是这样做也有坏处:

  1. 声明实例时需要很多预处理工作,而且数据量越大处理的时间越久
  2. 不支持某些数组操作
  3. 不支持其它数据类型,比如 Set、Map
  4. 不支持后续的数据观察

使用 Proxy 之后,以上问题全部都迎刃而解,甚至,因为 Proxy 是原生 API,性能表现更好,取得了内存减半、速度加倍的效果。

想了解更多 Vue 3.0 的新特性,可以去看我在 SF 的分享:迎接 Vue 3.0。(注:这是免费广告,我不会从新购用户取得收益。)


参考文章:

Constructor, operator “new”(构造函数和操作符 “new”)

分类
js 技术

使用 Proxy 添加魔术属性/方法

最近在开发我厂的 QA 工具时,遇到一个问题。我需要模拟 Puppeteer 的所有方法,以便兼容原先的 JS 文件。Puppeteer 提供一个 .asElement() 方法,可以把函数执行结果转换成一个伪 DOM Element(如果函数返回的就是 DOM Element 的话),然后我们就可以在 Node.js 里调用原本属于 DOM 的方法,比如 .focus()。Pupputeer 会替我们完成映射和函数调用,并且返回结果。

对于大部分对象来说,我只要模拟对应的属性、方法,然后用自己的函数实现功能即可。但是 DOM Element 有上百个属性和方法,手工实现一遍实在太低效了。必须寻找其它途径。

好在我之前看过 Proxy 的介绍,赶紧翻出文档和书又复习两遍,就大概知道怎么做了。

Proxy 类如其名,可以“代理”对某个对象的访问。你可以把他理解成明星的经纪人。明星成名之前都是自己处理一切事务,有了经纪人之后,大部分事务就由经纪人负责,但仍然有一些事情需要明星自己处理。

Proxy 的用法很简单,实例化时,把要代理的对象传进去,定义一下代理方法就好。

const obj = { name: 'meathill' };
new Proxy(obj, {
  get(target, property) {
    // 如果对象中有要求的属性或方法,则返回
    if (property in target) {
      return target[property];
    }
    // 没有的话,进行其它处理
    return 'hello';
  }
});

obj.name // 'meathill'
obj.age // 'hello'
obj.sex // 'hello'

接下来,比如我们访问 obj.foo,那么代理就会生效,它会先检查 obj,如果这个对象上本来就有 foo 属性,就会返回;如果没有,则会调用我们定义的方法来处理。

如此一来,我们可以定义一个 VElement 类,这个类可以实现一些特殊方法,比如 .type(str) 输入,.click() 点击等;然后用 Proxy 代理其它方法和属性,让对象进入插件 Context 执行。


关于如何创建具有魔法属性/方法的类,请移步阅读 使用 Proxy 创建有魔术属性/方法的类


Proxy 还有其它方法也很有用,尤其是 get 对应的 set ,以后再介绍。大家可以自己抽空研究下。

参考

  • 阮一峰的 ES6
  • 《深入理解 ES6》

分类
技术

第二次直播课程完结,兼谈对前端,对培训行业的看法

这篇文章写于上周,结果赶上服务器故障,写到一半去迁机器了……参见上篇文章

前些天播出了直播课程的第二讲,《Promise 的 N 种用法》,感觉播出本身还算成功。现场气氛不错,学员们积极提问,我做完解答,大家也纷纷表示听懂了,有收获。

如果人多点就更好了……

这里再打次广告,课程地址:https://segmentfault.com/l/1500000008757392。购买后可以任意回看,页面上还有推广链接,分享可以得实惠。如果大家想学 Promise 的用法,看这个视频,就够了,完全够了。

最开始策划这堂课的时候,把问题想简单了,结果做幻灯片的时候才发现,那句话:老师要想给学生一杯水,自己至少要有一桶水,古人诚不我欺。准备的时候花费了比第一次直播,比想象多得多的时间,讲试了两次,才得到了比较满意的结果。

这次购买的人数也比上次稍微多了几个,感谢亲友团支援,也希望更多的是真实用户吧!试讲的时候,在斗鱼也取得了不错的效果,增加了几个关注,还有人给了100鱼丸。以后录像的时候,都会选在斗鱼同步直播。如果能借机积累点人气的话就更好了。

总而言之,有进步。


做了两次直播之后,会有朋友问我说以后是不是就准备干这个了?关于这点我有几个想法跟大家分享。

选择这个时间去做前端培训,很合适

在过去很长一段时间里,前端这个行业,发展非常快。新技术新框架,层出不穷,快速更迭。对于我们这些从业者来说,确实不敢放松,甚至有些同学表示担心,学不过来。这个阶段我认为不能当导师,很可能误人子弟,既坑自己,又坑别人。

但是现在,情况有所改变。ES2017 增加的新特性数量,相对于 ES2015 而言,少得多。现在被大家津津乐道翘首以盼的,也就 Await/Async。CSS4 新增的特性,比 CSS3,也少很多。至于 ES2018,就更少了,写成博客都不带翻页的。这其实也很自然,就像 iPhone567,代际之间几乎没有什么大变化。因为过去的技术积累,其它语言当中有价值的特性,多半已经被前端吸收借鉴进来了。能够极大改善我们开发环境、生产环境的点,基本都已经被发掘过了。

未来的日子里,我们必须学会不断打磨手头已有的工具和技术,把思路从学习吸收,向深耕细作转换。比如用 ESlint 工具维护统一的代码风格,提升代码质量,提升安全系数,补足团队短板。或者,开拓前端的新边疆,比如 Hybrid,PWA,Electron 正在做的。

在这样的时刻,我觉得,可以停下来一段时间去做培训。正如我在上面所说,老师要给学生一杯水,自己至少要有一桶水。培训教学是非常好的总结、查缺补漏的手段。可以说利人利己。

换个角度,现在停下脚步做培训,一时半会儿也不会落后于行业。

终身学习的庞大市场

这是我的第一个想法,第二个想法从罗辑思维听来,便是所谓的“终身学习”。

在他的设想中,我们的未来,会是一个终身学习的时代,所有人都学习,并且是终身不断学习。我觉得至少在程序开发领域,他没说错,我自己就是例子。我2006年参加工作,做页面仔,切图。一边切一边学习 PHP,学习 JS,学习 AS,学习面向对象,学习开发模式,一路边学边干,走到现在。

我没有放松过学习,并且积极走出舒适领域,主动逼自己学习。回想我的学习历程,主要分两部分:

  1. 刚工作的时候,跟前辈学,靠脸皮厚,抱着大腿猛学
  2. 把他们学得差不多了,就只能自学,订阅博客,关注微博,以及看文档读源码

然而我发现,并不是所有人都能自学。比如前阵子认识个小妹,我发给她文档链接,她说看不懂英文……然而她必须学会,因为他们公司只有她一个前端。所以这又是一个非常大的市场,即由我,藉用我磁性的声音,清晰的普通话,带领他们学习,帮助他们学习。

并且现阶段来看,用户的付费意愿,也蛮强烈;付费习惯,也基本养成。

而且我觉得我周围的大多数人,还不太认可这一点,那么这也是一个机会。培训讲师常有,有10+年一线开发经验的资深前端工程师,愿意做培训讲师的,则不常有。

与时间赛跑

综合前面两点,我觉得,眼下这个时机,很适合切换到新跑道。毕竟以现在的我而言,一方面有前线工程师的视野和能力,另一方面又有专职讲师的时间和精力,并且我普通话说得好,声音稳定有磁性,天赋不能浪费。

希望接下来的时间里,能把讲师做好。说实话,现在课没人买没人听,我并不太担心。我觉得这是必经之路,想想郭德纲当年一后台人给一个听众(都不堪称“众”……)讲相声,也是这么熬过来的。我花了十多年时间才成为一个资深工程师,自然也要花很多时间,才可能成为一名优秀讲师,这很自然。同样借罗辑思维的话,终身学习知识服务,不是随随便便就能搞成的,一样需要专业人士专业服务。

所以现在我要做的,就是积累课程、积累用户、积累行业内的名气。我计划今年就干这个。所以我也是在跟时间赛跑:在全家饿死之前,把课程培训做起来。


最后再打个广告,明天我会开始第三次直播:《Web 永恒不变的主题:布局——Box,Flex,Grid》,这次要分享的内容是比较基础的 CSS 布局,除了回顾前几年妖魔鬼怪横行的布局 hack,还会展示 Flexbox 使用和最新的 Grid。对各种级别的前端同学都是很好的一堂课。欢迎大家光临,帮我推广也感激不尽。

另外我考虑接下来用3周时间,直播一个完整的项目,包括项目配置、webpack + babel + ES6、CSS 预处理、gulp 批处理,整个一套做下来,以及自动化测试、代码规范检查等等。这个得跟站方商量一下。

分类
js

Backbone + ES2015 Class

小型项目还是 Backbone 用起来比较舒心,所以写着写着又开始用它了。这次还用到 ES2015 和 Babel,于是疑问就来了:怎么在 ES2015 下使用 Backbone 呢?

普通用法

Backbone 自带 .extend() 方法,在早期的蛮荒岁月可以帮我们方便的创建各种子类。

var MyView = Backbone.View.extend({
  events: {

  },
  tagName: 'div',
  initialize: function () {

  }
});

以前这样确实很方便,代码也很好读。但是 ES2015 引入了新的 Class 规范,写出来是这样婶儿的:

class MyView extends Backbone.View {
  // 其实不能这样写
  tagName: 'div'
  events: {

  }
  // 上面这样写是不对的
  constructor() {

  }
}

这个时候问题来了。ES2015 不支持直接声明类属性,也就是上面代码中的 tagNameevents,是不能这样写的。因为它实际上只是重新包装了原型继承那一套东西,所以上面的代码实际上等效于:

MyView = function () {

}
MyView.prototype = new Backbone.View();
MyView.prototype.tagName = 'div';
MyView.prototype.events = {

};

这样的结果,不同实例间实际在共享方法和属性,包括 tagNameevents。如果是简单对象,比如字符串和数字,用 = 赋值还好;如果是复杂对象,比如数组,就很容易出问题。

Backbone 1.0 之前也会有这个问题,但是忘记从哪个版本开始就修复了。

新的用法

想要在 ES2015 中继续 Backbone 当然也是可以的。目前看来有三种方式:

  1. 把属性转化为方法。Backbone 对属性的处理都是委托给 _.result(),所以属性和方法的效果是一样的。
  2. 继续使用 MyClass.prototype.someProp = 'value';
  3. 将初始化对象放在构造函数中。

我个人比较喜欢第三种方法,因为它更接近之前的写法,而且结合 Babel、Webpack 等编译打包工具,也可以做到私有。

class MyView extends Backbone.View {
  constructor(init) {
    super(_.extend(init, {
      events: {

      },
      tageName: 'div'
    });
  }
}

未来,ES2016

ES2016 中引入了很重要的 Decorators 概念,顾名思义,它会显式的告诉运行时下面是些什么东西,那么运行时自然也就可以按照对应的规则去处理。

@props({
  tagName: 'div',
  events: {

  }
})
class MyView extends Backbone.View {
  constructor(init) {
    super(init);
  }
}

参考文章

本篇内容主要参考自以下两篇: