默认分类
只是一个默认分类

【译】为什么我们选择Angular 2 而不是 React用于我们的企业级应用开发

作者:Justin Goodhew
原文链接

1-xWwEzrjwXOsA3zZTV8ptNA@2x.jpeg

由于这在开发者社区中是一个非常有争议的话题,所以随着我们更深入的理解并结合收到的反馈,我们将不断更新这篇博客。我也想推出一些限定词,所以大家都明白我们来自哪里。

我们的背景

我们的公司由我的联合创始人和首席技术官,瑞恩·坎贝尔领导,一位拥有18年的经验的软件架构师,过去10年在大型企业。 当我们决定使用Angular 2时,他已经熟悉Angular 1了。但是,在决定使用Angular之前,我们还在React中构建了一个应用程序。

我们也相信Angular和React都是伟大的选择,并允许一些惊人的产品(React: Messenger和Angular: healthcare.gov)在其上建立和发展。 根据具体项目,团队以及他们的经验/喜好,制定更加切合实际的决策。 本文的目的是让您了解我们的思想过程,并了解我们采用Angular的旅程中的一些发现。

以下是两种技术的简要概述:

Angular 2 —由Google支持,是最新的JS(javascript)框架。

主要特点:

  • 坚持己见—用“Angular 方式”写东西
  • Typescript 通常能提高效率
  • 有很多由Google支持的样板文件和可重复使用的组件

React —由Facebook支持,是一个在JS语言中的库,而不是一个框架。

主要特点:

  • 开放—React允许开发人员更多的灵活性和自由度
  • 更自由的选择
  • 更新和频繁的更改

库 vs. 框架

最大的区别是它们是如何组织的。 React是一个 ,angular是一个 框架。 定义如下:

  • 库:我将使用React库,现在我必须决定所有其他将要用到的支持库:路由,model 管理和构建工具。
  • 框架:我将使用Angular 2,让我们开始用Angular 2的方式构建一个应用程序。

因此,我们不会将Angular 2与独立的React进行比较,我们正在与React以及获得与Angular 2功能相同的所有其他软件包(类似React + Redux + React router + Babel)进行比较。

我们的 React 经验

当我们在React中构建一个应用程序时,我们真的喜欢这个库。我们对所有的可能性感到兴奋,前期学习远低于Angular。当我们开始构建我们的第一个React Native应用程序时,我们的团队对React的经验有限,而且生态系统频繁更新。

我们知道我们可以使用React来快速创建一个MVP,这是非常重要的,因为这个项目的排期很短。我们的第一步是获得项目设置,这意味着找到正确的配套工具进行集成。这是我们犯了重大错误的地方。我们选择使用 @exponent/ex-navigator 一个路由器库,它似乎有很好的支持,是由exponentjs开发的,他们以React生态系统中的优质工具而闻名。

我们开始整理应用程序的原型,直到我们开始尝试传递数据的应用程序,我们注意到,这个路由器是不实用和废弃的。你必须改变state ,通过导航器传递它,这是你不应该在现代React应用程序中做的第一件事情。

这个经历让我们对使用React构建应用程序感到失望。 React本身是令人惊讶的,在我们的团队中没有一个关于React本身的意见。不幸的是,我们选择的库却不可同日而语。但这是我们自己的错误,如果你对你的初始选择更加谨慎,你可以避免这些问题。然而,总是存在你正在使用的库被废弃的风险。

我们的 Angular 2 经验:

我们用 Angular 构建了多个应用。 Angular 2框架中的所有部分,包括高级架构都由Google提供支持。这真的帮助大家融入同一个页面,标准化大团队,但它也导致了一些问题。

对于初学者,需要很多思考才能入门Angular 2和它提供的每个模块。它感觉像一个企业开发人员会熟悉的高质量框架。它正在推动基于Web的应用程序框架中预期的界限。 我们一直在使用jQuery甚至是Backbone来开发应用程序。我们的团队喜欢Angular 2 的依赖注入,使用响应式编程可观察模式构建的内置即用型HTTP模块,一个精心设计的Angular 2 升级程序允许将大型项目逐个移动到Angular 2。

我所遇到的唯一缺点是框架的一切还没有完全被记录下来。 到目前为止,Angular的文档质量很好,但是只能涉及到许多Angular 2模块的表面。作为一个团队,我们通过深入了解Angular 2源代码并分享我们的知识,成为专家,一起工作。

一般来说,Angular 2给我们留下了深刻的印象,它是我们的首选框架。我们已经能够比过去的框架更容易完成功能。 TypeScript,记录的最佳实践,以及Angular 2架构使构建应用程序更愉快。我们能够了解OOP,设计模式和最佳实践架构,并将其应用于web。

为什么Angular更好?

我们的客户主要是希望从开源获益的大型企业。 当选择软件时,他们关心通过以下方式降低风险:

  • 技术一致性
  • 社区支持
  • 清晰的最佳实践
  • 最小未知

他们有数百万的用户,以及数百万或数十亿美元,受他们管理的软件影响。 这些决定是至关重要的,有时甚至关乎生死。

他们还坚持使用一种技术很长时间,需要一致性,清晰的最佳实践和最小未知。

必须降低技术风险,以允许企业利用开源的力量,并从开源社区提供的惊人的工作中获益。

企业如何量化风险?

企业在三个不同领域量化风险:支持,社区和风险。 当在这些领域中比较React和Angular 2时,根据企业的需求,你可以更好地了解为什么Angular 2有利于他们。

周边的技术支持

React —中断支持很快,保持更新是很困难的

Angular 2 —Google公开支持时间和版本迭代安排:2.0版之后没有更多突破性变化

可雇佣

React

  • 大量社区追随者,会有大量人才储备。
  • 更少标准化/更多培训 - 由于技术的开放性开发者将以自己的方式构建。

Angular

  • 大量社区追随者,会有大量人才储备。
  • 上手简单 - 开发人员将遵循类似的最佳实践。
  • 标准化 - Google撰写了如何使用框架的最佳实践。

被支持者/创造者遗弃的潜在风险

React

  • 支持 - 高度支持,增长的社区。
  • 依赖他人 - 高。 库由Facebook支持,但其他需要的部分基本由第三方建立。

Angular

  • 支持 - 高度支持,增长的社区。
  • 依赖他人 - 全面支持。 最小化依赖于除Google和他们的开发团队之外的其他人。

结论

基于我们与React和Angular 2开发的经验以及客户的需求,我们决定使用Angular。 这并不意味着我们不能接受新的选择,但是现在,对于我们的客户,Angular符合他们的需求,我们对结果感到非常激动。

我们知道有很多关于这个问题的意见,所以请随时让我们知道你选择了什么,原因为何。 我们将深入探讨这两个框架中的调试和开发工具,以便更新。


Biznas.io
Biznas on Twitter
Biznas on Facebook
Biznas on LinkedIn

策略模式

策略模式是对算法的封装,把一系列的算法分别封装到对应的类中,并且这些类实现相同的接口,相互之间可以替换。

其实就好比USB接口,鼠标、键盘就是算法,插上什么设备就拥有了什么功能。

对于前端来说最常见的就是表单验证逻辑了,把各种各样的验证逻辑封装成策略对象,然后输入框需要什么验证方法,给他安上就好了。

策略模式的结构

封装类:也叫上下文,对策略进行二次封装 ,目的是避免高层模块对策略的直接调用。
抽象策略:通常情况下为一个接口,当各个实现类中存在着重复的逻辑时,则使用抽象类来封装这部分公共的代码,此时,策略模式看上去更像是模版方法模式。
具体策略:具体策略角色通常由一组封装了算法的类来担任,这些类之间可以根据需要自由替换。

例子

下面使用Typescript实现的例子:

interface IStrategy {
    doSomething(): void;
}
class ConcreteStrategy1 implements IStrategy {
    public doSomething() {
        console.log("具体策略1");
    }
}
class ConcreteStrategy2 implements IStrategy {
    public doSomething() {
        console.log("具体策略2");
    }
}
class Context {
    private strategy: IStrategy;
    public execute() {
        this.strategy.doSomething();
    }
    constructor(strategy: IStrategy) {
        this.strategy = strategy;
    }
}

let context = new Context(new ConcreteStrategy1());
context.execute();

其实策略模式在原生js中更简单,因为函数也是对象,所以我们直接把策略定义为对象:

const strategies = {
    "ConcreteStrategy1" = function() {
        console.log("具体策略1");
    },
    "ConcreteStrategy2" = function() {
        console.log("具体策略2");
    },
}

const context = function(ConcreteStrategy){
    return strategies[ConcreteStrategy]();
}

context(ConcreteStrategy1) //"具体策略1"

优点:

  • 策略类之间可以自由切换,由于策略类实现自同一个抽象,所以他们之间可以自由切换。
  • 易于扩展和复用,增加一个新的策略对策略模式来说非常容易,而且封装的算法还可以用在其它的地方。
  • 避免使用多重条件判断来决定使用哪一种算法,更易于维护。

缺点:

  • 必须对客户端(调用者)暴露所有的策略类,因为使用哪种策略是由客户端来决定的,因此,客户端应该知道有什么策略,并且了解各种策略之间的区别,否则,后果很严重。例如,有一个排序算法的策略模式,提供了快速排序、冒泡排序、选择排序这三种算法,客户端在使用这些算法之前,是不是先要明白这三种算法的适用情况?再比如,客户端要使用一个容器,有链表实现的,也有数组实现的,客户端是不是也要明白链表和数组有什么区别?就这一点来说是有悖于迪米特法则(最少知识原则)的。

参考文献:
策略模式

【译】使用Zone.js和FakeAsync控制时间

JavaScript应用程序充满了异步代码:我们侦听事件,使它们去抖,与web workers交互,向后端发送消息。测试这样的代码是困难的,并且在一些情况下几乎不可能。 在这篇博文中,我将展示测试异步代码的传统方式,以及如何通过使用Zone.js和fakeAsync,以及通过控制时间本身,我们可以使异步测试更直接和可靠。

案例分析

让我们来看看这段代码示例:

interface PurchaseBackend {
  checkAvailability(sku: string, qty: number): Promise<boolean>;
  order(sku: string, qty: number): Promise<boolean>;
}

interface Messages {
  success(message: string): void;
  error(message: string): void;
}

class PurchaseService {
  inProgress = false;

  constructor(private backend: PurchaseBackend, private messages: Messages) {}

  order(sku: string, qty: number): void {
    if (this.inProgress) {
      this.messages.error("Already purchasing an item");
      return;
    }

    this.backend.checkAvailability(sku, qty).then((enoughQuantity: boolean) => {
      if (enoughQuantity) {
        this.inProgress = true;
        return this.backend.order(sku, qty);
      } else {
        return false;
      }

    }).then((success: boolean) => {
      if (success) {
        this.messages.success("Your purchase succeeded!");
      } else {
        this.messages.error("Your purchase failed!");
      }

    }).then(() => {
      this.inProgress = false;
    }, () => {
      this.inProgress = false;
    });
  }
}
  • PurchaseService协调checkAvailability和订单调用。 它询问是否有足够数量的特定sku可用,如果可用,请订购。 此外,它在字段中跟踪订单的状态。
  • 消息表示一些抽象的UI,这里是使框架独立的示例。 如果它是一个Angular应用程序,这可以是一个Angular组件。
  • 不出所料,PurchaseBackend代表后端。

测试

我们要编写一个测试PurchaseService验证该INPROGRESS字段设置为真后,我们检查其可用性,而且它仍然是真实的,直到我们完成订单。
这是一种方法。

分析测试

这里有很多东西要解开。
首先,我们有承诺的一个链条,我们需要插入的状态PurchaseService在这条产业链的中间,也就是说,没有承诺,我们可以等待运行断言。为了能够做到这一点,我们协调两个promise的完成,并使用两个嵌套的setTimeout调用。
我们能够避免一些吧,使测试通过每一个中间结果存储在的某些属性更好PurchaseService,但是这将让服务更加有状态的,复杂的。使代码可测试应该改善其设计,而不是其他方式!
注意,测试中的服务实际上相当简单,很容易想象一个协调多个异步进程,其中一些将被表示为observables和超时。测试这样的服务将更加困难,并且测试将更加难以遵循。
但等待 - 它变得更糟。如果抛出异常,并且其中一个promise被拒绝,Jasmine将等待测试超时,从而导致糟糕的开发体验。如果我们把一些去抖动混入,我们实际上必须等待几百毫秒的测试完成。
最后,在运行任何断言之前,验证所有计划的异步函数是否完成通常是一个好主意。这是不可能做到的,至少在一般情况下,这种风格的测试。

解决方案:Zone.js

解决方案是控制时间本身!

许多图书馆试图这样做。例如,我们可以为RxJS提供一个自定义调度程序。然而,这样的解决方案不是全面的:它们针对图书馆,因此是过高的级别。

该Zone.js库采用不同的方法:它会低一个级别和补丁浏览器。这是为了使所有异步原语(例如,setTimeout,Promise)通过一个单独的地方,这允许我们使用一个机制来控制时间,而不管我们使用的库。

0-QDXohxNhXdtH4Px--.png

我们开发了Zone.js作为Angular项目的一部分。最初的目标不是控制时间,而是知道何时异步操作完成,所以我们可以运行Angular的变化检测和更新视图。这就是为什么新版本的Angular不再需要$ scope.apply  -  Zone.js告诉Angular何时检查更改,它为用户透明地执行。事实上,Zone.js也可以用来控制测试中的时间是一个副产品,但是一个非常好的。

FakeAsync

添加Zone.js到页面补丁的浏览器,并创建一个全局 Zone 对象,我们可以用它来与库进行交互。的默认实现园区包装浏览器异步原语:因此调用包裹的setTimeout会做一些区相关的工作,但最终将调用浏览器的setTimeout的。

但是 Zone 有一个不同的实现称为FakeAsync。它保存所有的调用到一个数组,而不是调用浏览器。所以所有的计划异步工作都存储在这个数组中。然后它可以通过遍历该数组并调用存储的函数来模拟时间的流逝。

0-SW-FFXeQs_B8JTjL-.png

所以当使用FakeAsync时,浏览器的setTimeout永远不会被调用。这意味着使用的测试FakeAsync将始终在单个microtask执行。这样的测试感觉同步或阻塞,即使在技术上不是这样。

辅助函数:fakeAsync,tick,flushMicrotasks。

即使FakeAsync不绑定到Angular,框架提供了一些帮助函数,使使用这个 zone 更容易:fakeAsync,tick和flushMicrotasks。

我将在下面的例子中使用这些辅助函数,所以我将从Angular包中导入它们。但是如果你是另一个UI框架的愉快用户,你总是可以移植它们(大约100行代码)。

import {fakeAsync, flushMicrotasks} from '@angular/core/testing';

describe("PurchaseService", () => {
  it("fakeAsync test", fakeAsync(() => {
    const b = jasmine.createSpyObj("PurchaseBackend", ['checkAvailability', 'order']);
    const m = jasmine.createSpyObj("Messages", ['success', 'error']);

    b.checkAvailability.and.returnValue(Promise.resolve(true));

    let orderResolve = null;
    const orderResult = new Promise(r => orderResolve = r);
    b.order.and.returnValue(orderResult);

    const p = new PurchaseService(b, m);

    p.order("Sku123", 10);
    flushMicrotasks();
    expect(p.inProgress).toBe(true);

    orderResolve(true);
    flushMicrotasks();

    expect(p.inProgress).toBe(false);
    expect(m.success).toHaveBeenCalledWith("Your purchase succeeded!");
  }));
});

让我们逐步理解这个测试。

我们将测试包装到fakeAsync调用中,以确保在测试完成后没有异步工作未完成。

然后,如上面的测试,我们调用p.order。但在这种情况下,它不计划真正的微任务,而是将其添加到FakeAsync的微任务队列。

在这一点上,我们基本上被阻塞在:

this.backend.checkAvailability(sku, qty).then((enoughQuantity: boolean) => {
  if (enoughQuantity) {
     this.inProgress = true;
     return this.backend.order(sku, qty);
  } else {
     return false;
  }
  //…

调用flushMicrotasks运行传入的函数,然后将inProgress设置为true,并将另一个微任务添加到队列中。第一个微任务现在完成,并从队列中删除。

虽然被阻塞,我们正在按顺序完成promise。调用flushMicrotasks第二次运行调度的微任务,这完成了整个操作。

如果我们在测试完成后在队列中有微任务,我们会得到一个错误。例如,以下测试会报错:

import {fakeAsync, flushMicrotasks} from '@angular/core/testing';

describe("PurchaseService", () => {
  it("fakeAsync test", fakeAsync(() => {
    const b = jasmine.createSpyObj("PurchaseBackend", ['checkAvailability', 'order']);
    const m = jasmine.createSpyObj("Messages", ['success', 'error']);

    b.checkAvailability.and.returnValue(Promise.resolve(true));

    let orderResolve = null;
    const orderResult = new Promise(r => orderResolve = r);
    b.order.and.returnValue(orderResult);

    const p = new PurchaseService(b, m);

    p.order("Sku123", 10);
    flushMicrotasks();
    expect(p.inProgress).toBe(true);

    orderResolve(true);
  }));
});

Analyzing the Test

First, the test is flat: no setTimeout calls required. Flat tests are easier to follow, refactor, and debug.
Second, if one of the promises threw an exception, the corresponding flushMicrotasks call would throw that exception instantly, which results in a better dev experience.
Third, fakeAsync verifies that no pending microtasks or timeouts left after the test is complete, again resulting in a better dev experience.
And, finally, testing debouncing and other time-related operations is just as easy. Instead of calling flushMicrotasks, we can call tick and pass the number of milliseconds we want to skip.

Zone.js in the Browser

This is not the only application of Zone.js. As I mentioned, the new versions of Angular use it to trigger change detection. It can also be used to enhance error reporting, implement long stack traces, etc.. Since it is such a useful primitive, it is slowly making its way into the browser (see here https://gist.github.com/mhevery/63fdcdf7c65886051d55). So think of Zone.js as a prollyfill.

Summary

As JavaScript developers we write a lot of asynchronous code. In this article I showed that such code is hard to test, and testing even a fairly simple service requires promise coordination and nested setTimeout calls. The solution is to take control of time with the help of Zone.js. This results in flat tests that run in a single microtask. Such tests are easier to follow, change, and debug.

【译】观察者模式

观察者是一种设计模式,其中对象(被称为主体)根据它(观察者)维护对象的列表,在他们的状态发生任何改变会自动通知观察者。当主体需要通知观察者有关发生的事情时,它向观察者广播通知(其可以包括与通知主题相关的特定数据)。当我们不再希望特定观察者被通知他们注册的主题的改变时,主体可以将它们从观察者列表中删除。

观察者与发布/订阅模式之间的差异

虽然Observer模式是有用的,需要注意,通常在JavaScript世界中,我们会发现它通常使用一个变体称为发布/订阅模式实现。虽然非常相似,这些模式之间有差异值得注意。

Observer模式要求希望接收主体通知的观察者(或对象)必须将此兴趣订阅到触发事件的对象(主体)。

然而,发布/订阅模式使用位于希望接收通知(订阅者)的对象和发起事件的对象(发布者)之间的主体/事件通道。该事件系统允许代码定义应用程序特定事件,其可以传递包含订户所需的值的自定义参数。这里的想法是避免订阅者和发布者之间的依赖。

这与观察者模式不同,因为它允许任何实现适当的事件处理程序的用户注册并接收由发布者广播的主题通知。

优点

观察者和发布/订阅模式鼓励我们认真思考我们应用程序的不同部分之间的关​​系。它们还帮助我们识别包含直接关系的层,而不是用主体和观察者的集合来替换。这有效地可以用于将应用分解成更小,更松散耦合的块,以改进代码管理和重用的潜力。

使用Observer模式的另一个动机是我们需要保持相关对象之间的一致性,而不使类紧密耦合。例如,当对象需要能够通知其他对象而不对这些对象做出假设时。

当使用任一模式时,观察者和主体之间可能存在动态关系。这提供了大量的灵活性,当我们的应用的不同部分紧密耦合时,这可能不容易实现。

虽然它并不总是每个问题的最佳解决方案,但这些模式仍然是设计解耦系统的最佳工具之一,应该被任何JavaScript开发人员视为重要的实用工具。

缺点

其实,这些模式的一些问题实际上源于它们的主要益处。 在发布/订阅中,通过将发布者与订阅者分离,有时可能难以获得对我们的应用程序的特定部分如我们期望的功能的保证。

例如,发布者可以假设一个或多个订阅者正在监听它们。 假设我们使用这样的假设来记录或输出关于某些应用程序进程的错误。 如果执行记录崩溃(或由于某些原因失败)的订户,由于系统的解耦特性,发布者将没有办法看到这种情况。

模式的另一个缺点是用户对彼此的存在相当无知,并且忽视了切换发布者的成本。 由于订阅者和发布者之间的动态关系,依赖的更新可能难以跟踪。

发布/订阅的实现

发布/订阅非常适合JavaScript生态系统,主要是因为在核心,ECMAScript实现是事件驱动。 这在浏览器环境中尤其如此,因为DOM使用事件作为其主要的交互API来编写脚本。

也就是说,ECMAScript和DOM都不提供用于在实现代码中创建自定义事件系统的核心对象或方法(除了DOM3 CustomEvent,它绑定到DOM,因此不是通用的)。

幸运的是,流行的JavaScript库如jQuery(自定义事件)已经有了一些实用程序,可以帮助轻松实现一个Publish / Subscribe系统。 下面我们可以看到一些例子:

// 发布
// jQuery: $(obj).trigger("channel", [arg1, arg2, arg3]);
$( el ).trigger( "/login", [{username:"test", userData:"test"}] );

// 订阅
// jQuery: $(obj).on( "channel", [data], fn );
$( el ).on( "/login", function( event ){...} );

// 取消订阅
// jQuery: $(obj).off( "channel" );
$( el ).off( "/login" );

注:Zepto的trigger方法仅支持在dom元素上触发事件。

一个发布/订阅的实现

var pubsub = {};
 
(function(myObject) {
 
    // Storage for topics that can be broadcast
    // or listened to
    var topics = {};
 
    // An topic identifier
    var subUid = -1;
 
    // Publish or broadcast events of interest
    // with a specific topic name and arguments
    // such as the data to pass along
    myObject.publish = function( topic, args ) {
 
        if ( !topics[topic] ) {
            return false;
        }
 
        var subscribers = topics[topic],
            len = subscribers ? subscribers.length : 0;
 
        while (len--) {
            subscribers[len].func( topic, args );
        }
 
        return this;
    };
 
    // Subscribe to events of interest
    // with a specific topic name and a
    // callback function, to be executed
    // when the topic/event is observed
    myObject.subscribe = function( topic, func ) {
 
        if (!topics[topic]) {
            topics[topic] = [];
        }
 
        var token = ( ++subUid ).toString();
        topics[topic].push({
            token: token,
            func: func
        });
        return token;
    };
 
    // Unsubscribe from a specific
    // topic, based on a tokenized reference
    // to the subscription
    myObject.unsubscribe = function( token ) {
        for ( var m in topics ) {
            if ( topics[m] ) {
                for ( var i = 0, j = topics[m].length; i < j; i++ ) {
                    if ( topics[m][i].token === token ) {
                        topics[m].splice( i, 1 );
                        return token;
                    }
                }
            }
        }
        return this;
    };
}( pubsub ));

原文地址:https://addyosmani.com/resources/essentialjsdesignpatterns/book/#observerpatternjavascript

装饰器模式与ES7中的装饰器

装饰器能动态地给一个对象添加一些额外的职责,换句话说就像Chrome的插件,能够扩展浏览器的功能,想加什么功能下载一个插件来“装饰”浏览器就可以了。
所以装饰器的目的就是功能的复用,因为在传统的面向对象语言中,为对象添加功能通常会用继承的方式,但继承真的就那么完美吗,其实不然。当超类改变,子类也随之改变,耦合了;超类内部细节对子类可见,破坏封装了;功能复用,子类=对象*功能.....

使用AOP装饰函数

//前置装饰
Function.prototype.before = function (beforefn) {
    var __self = this; // 保存原函数的引用
    // 返回包含了原函数和新函数的"代理"函数
    return function () {
        // 执行新函数,且保证this 不被劫持,新函数接受的参数
        // 也会被原封不动地传入原函数,新函数在原函数之前执行
        beforefn.apply(this, arguments);
      
        // 执行原函数并返回原函数的执行结果,并且保证this 不被劫持
        return __self.apply(this, arguments);
    }
};

//A = A.before( B );

当然装饰器也不是完美的,多层装饰比较复杂,也可能会导致性能问题。

可以这么理解:继承是自上而下,装饰器是由内而外包裹的关系。

ES7中的装饰器

装饰器由一个@紧接一个函数名称,ES7 中的 decorator 其实就是一种语法糖,它依赖于 ES5 的 Object.defineProperty 方法 。

Object.defineProperty

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。

Object.defineProperty(obj, "key", {
  enumerable: false,
  configurable: false,
  writable: false,
  value: "static"    //属性对应的值
});
\\Object.defineProperty(obj, prop, descriptor)
\\obj: 目标对象
\\prop: 属性名
\\descriptor: 针对该属性的描述符

实现一个 decorator

function readonly(target, key, descriptor) {
  descriptor.writable = false
  return descriptor
}

class Dog {
  @readonly
  bark () {
    return 'wang!wang!'
  }
}

let dog = new Dog()
dog.bark = 'bark!bark!'
// Cannot assign to read only property 'bark' of [object Object]

编译之后的代码:

function Dog () {}

Object.defineProperty(Dog.prototype, 'bark', {
  value: function () { return 'wang!wang!' },
  enumerable: false,
  configurable: true,
  writable: true
})

ES7 的 decorator,作用就是返回一个新的 descriptor,并把这个新返回的 descriptor 应用到目标方法上。

更多的装饰器,可以参考core-decorator,它提供了一些常用的 decorator。

参考阅读:
1.Object.defineProperty()
2.Decorators in ES7