My ECMAScript 7 wishlist

With ECMAScript 6 now feature complete, any further changes to the core of JavaScript will happen in ECMAScript 7. I’m pretty excited about the changes coming in ECMAScript 6 and there are already some great ECMAScript 7 features such as Object.observe() and asynchronous functions. While the development of ECMAScript 7 continues, I just wanted to share my personal wishlist of things that would make writing JavaScript even better and are (seemingly) within the scope of consideration for ECMAScript 7.

Some notes about the wishlist items:

  • I haven’t found a good source of already-scheduled ECMAScript 7 features, so I don’t know if any of these are already on the roadmap or not.
  • I don’t actually care what the names of things are, I just care about the functionality.
  • I’m no expert in syntax. It’s entirely possible I suggested something here that isn’t feasible.

Arrays

I recently came to realize that I spend an inordinate amount of time working with arrays in JavaScript, moreso than anything else. I’ve had a growing list of annoying things about working with arrays that have been partially solved in ECMAScript 5 and 6. However, there still seems to be some low-handing fruit.

Array.prototype.last(), Array.prototype.first()

The number of times I write something like items[items.length - 1] each week drives me crazy. I just want a last() method that does it for me. The native equivalent of this:

Array.prototype.last = function() {
    return this[this.length - 1];
};

While I check the last item of arrays frequently, I also check the first item frequently. So I’d love to have first() as well:

Array.prototype.first = function() {
    return this[0];
};

With these two methods, a lot of my code would look cleaner:

//before
if (items[0] === "(" && items[items.length - 1] === ")") {
    // do something
}

// after
if (items.first() === "(" && items.last() === ")") {
    // do something
}

Array.prototype.isEmpty()

Another thing I do with arrays a lot is check to see if it’s empty by comparing the length to zero. I’d much rather have a method to improve readability. Something like this:

Array.prototype.isEmpty = function() {
    return this.length === 0;
}

Function.empty

I find myself using empty functions frequently, especially in tests and callback-oriented functions where I don’t actually care to wait for the results. That means I usually write things like:

someAsyncMethod(function() {
    // noop
});

The // noop comment is there to make sure people understand I intentionally left this function empty. I’d much rather there be a predefined empty function that I can reuse whenever I want a throwaway function, such as:

someAsyncMethod(Function.empty);

// where...
Object.defineProperty(Function, "empty", {
    value: () => {},
    writable: false,
    configurable: false,
    enumerable: true
};

Object.deepPreventExtensions(), Object.deepSeal(), Object.deepFreeze()

ECMAScript 5 added Object.preventExtensions()Object.seal(), and Object.freeze(). These serve to protect objects from certain types of modification, which is fantastic, except that these are shallow operations. For instance:

var data = {
    subdata: {
        type: "js"
    }
};

Object.freeze(data);

data.subdata = {};   // fails silently in nonstrict mode

data.subdata.type = "css";   // succeeds

This is working as intended, data.subdata cannot be overwritten but data.subdata.typecan be since Object.freeze() only freezes the properties of the object that is passed. In most cases, that’s okay, but I’ve found myself needing to apply object protection deeply, and it would be great to have official methods that did this.

My primary use case is in reading in a JSON configuration and wanting to protect it throughout the lifetime of the application. It’s possible to implement this fairly easily in ECMAScript 6:

Object.deepPreventExtensions = function(object) {

    // for avoiding circular references
    var handled = new WeakSet();

    // recursive function
    function deepPreventExtensions(object) {

        // handle first level
        Object.preventExtensions(object);
        handled.add(object);

        Object.keys(object).filter(function(key) {
            // get keys for objects not already handled
            return object[key] && (typeof object[key] === ‘object‘) && !handled.has(object[key]);
        }).forEach(function(key) {
            Object.deepPreventExtensions(object[key]);
        });
    }

    deepPreventExtensions(object);
};

The only tricky part is handling circular references, but that is made somewhat easier by using a WeakSet to track already-handled objects. The same basic pattern can be applied forObject.deepSeal() and Object.deepFreeze().

Defensive objects

I recently wrote a post about defensive objects. As a refresher, defensive objects are those that throw an error when you try to read a property that doesn’t exist. This is the way objects work in type safe languages and is the last missing capability for accurately creating classes in JavaScript that behave as they would in other languages.

Today, you can get pretty close:

class Person {

    constructor(name) {
        this.name = name;
        Object.seal(this);
    }
}

Using the ECMAScript 6 class syntax plus Object.seal(), you’re able to create an object that can’t have its properties removed or new properties added. However, accessing a nonexistent property will still just return undefined:

var me = new Person("Nicholas");
console.log(me.nme);      // unfortunate typo, returns undefined

Because the property nme doesn’t exist, it returns undefined when you try to access it. I recently spent a half hour tracking down a bug that was a typo of this nature and wished I had a way to prevent it from happening.

Adding this behavior would bring object properties inline with variables in terms of what will happen when you try to access something that doesn’t exist. An error is thrown when you try to read an undeclared variable; I’d like that same behavior when you try to read an undeclared property.

I propose a method that is similar to Object.preventExtensions(), perhaps calledObject.preventUndeclaredGet() (probably not the best name) that would set an internal property on an object changing the [[Get]] behavior to throw an error when the given property doesn’t exist. For example:

class Person {

    constructor(name) {
        this.name = name;
        Object.seal(this);
        Object.preventUndeclaredGet(this);
    }
}

var me = new Person("Nicholas");
console.log(me.name);  // "Nicholas"
console.log(me.nme);   // throws error

Adding this capability allows you to create classes that correctly mimic classes in other languages. Also, if you don’t seal the object, you can add new properties whenever you want; as long as you set the property value before reading it, no error will occur.

Custom descriptor attributes

Property descriptors seem like a great way to add meta information to properties except that you cannot add unknown properties. JavaScript always returns only the spec-defined attributes when you try to store a custom piece of information:

var me = {};
Object.defineProperty(me, "name", {
    value: "Nicholas"
    type: "string"
});

var descriptor = Object.getOwnPropertyDescriptor(me, "name");
console.log(descriptor.value);    // "Nicholas"
console.log(descriptor.type);     // "undefined"

To me, the property descriptor is a great possible location for storing information related to a particular property. Besides the implications for storing type hints, you could also store relevant information about validation, data bindings, or more.

It wouldn’t make sense to allow just any arbitrary attributes on the descriptor, as the language might need to add more in the future. However, adding a single property that is designed for custom information could work. For instance, what if the spec declared a property called metato contain user-defined information. That meta would be stored and could later be retrieved exactly as-is, without the possibility of affecting the other property descriptor values or risk naming collisions with future property descriptor attributes. For example:

var me = {};
Object.defineProperty(me, "name", {
    value: "Nicholas"
    meta: {
        type: "string"
    }
});

var descriptor = Object.getOwnPropertyDescriptor(me, "name");
console.log(descriptor.value);     // "Nicholas"
console.log(descriptor.meta.type); // "string"

Lightweight traits

In many ways, JavaScript has supported traits for a long time through the use of mixins. Traits are really the same thing: objects that provide a set of methods intended to be applied to another object. The Object.assign() method was added in ECMAScript 6 to aid in this endeavor. However, it can get quite messy to use this approach:

var trait1 = {
    method1: function() {}
};

var trait2 = {
    method2: function() {}
};

function MyObject() {
    // ...
}

Object.assign(MyObject.prototype, trait1, trait2, {
    method3: function() {}
});

There’s no way to easily do the same thing with ECMAScript 6 classes, so you’d be stuck callingObject.assign() in the constructor and applying it to each instance.

What I’d like to propose is some syntactic sugar to make this easier using object literals and classes. For object literals, it would look like this:

function MyObject() {
    // ...
}

// lightweight traits
MyObject.prototype = {

    use trait1,
    use trait2,

    method3: function() {}
};

// desugars to
MyObject.prototype = Object.assign({}, trait1, trait2, {
    method3: function() {}
});

A similar syntax can be used in ECMAScript 6 classes to specify traits for the prototype:

class MyObject {
    use trait1;
    use trait2;

    constructor() {}

    method3() {}
}

// desugars to

function MyObject() {
    // ...
}

Object.assign(MyObject.prototype, trait1, trait2, {
    method3: function() {}
});

It’s entirely possible that Object.assign() should actually be something else, perhaps something that also calls toMethod() so the super binding is correct, but I think this example illustrates my point.

Conclusion

I’m very excited to see where ECMAScript 7 is headed and hope that some of these ideas are worthwhile enough to pursue. Even if they aren’t, ECMAScript 6 is such a superior upgrade from ECMAScript 5 that I’m sure ECMAScript 7 will be a really great set of changes as well.

Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox PublishingO‘Reilly Publishing, or anyone else. I speak only for myself, not for them.

Both comments and pings are currently closed.

Related Posts

时间: 2024-10-10 08:31:37

My ECMAScript 7 wishlist的相关文章

JavaScript周报#184

This week’s JavaScript news Read this issue on the Web | Issue Archive JavaScript Weekly Issue 184June 6, 2014 Editor: Peter Cooper   Featured Create a TV Show Tracker using AngularJS, Node.js and MongoDB — A superbly thorough and well-presented tuto

ECMAScript 5 Array Methods

ECMAScript 5 定义了9个新的数组方法,分别为: 1.forEach();  2.map();  3.filter();  4.every();  5.some();  6.reduce();  7.reduceRight();  8.indexOf();  9.lastIndexOf(); 概述:首先,大多数的方法都接受一个函数作为第一个参数,并为数组里的每个元素(或者一些元素)执行这个函数.在稀疏数组中(索引不以0开始,并且元素不连续),不存在的数组元素不调用函数参数.大多数实例中

前端开发者进阶之ECMAScript新特性--Object.create

前端开发者进阶之ECMAScript新特性[一]--Object.create Object.create(prototype, descriptors) :创建一个具有指定原型且可选择性地包含指定属性的对象 参数:prototype 必需.  要用作原型的对象. 可以为 null.descriptors 可选. 包含一个或多个属性描述符的 JavaScript 对象."数据属性"是可获取且可设置值的属性. 数据属性描述符包含 value 特性,以及 writable.enumerab

ECMAScript基本数据类型

ECMAScript有5种基本数据类型 Undefined.Null.Boolean.Number 和 String. Undefined类型 未声明.声明但未初始化的变量typeof判定数据类型的时候都是 undefined 声明但未初始化的变量可以对其进行undefined值类型可以执行的操作 未声明的变量对其使用非赋值操作都会出错 var JsTest=function(){ var str; console.log(str==undefined);//true console.log(s

ECMAScript 6 小结

一.测试环境 1.node下几乎完全支持ES6,标准浏览器支持部分语法 2.chrome下使用ES6,为了保证可以正常使用大部分语法,需要使用严格模式,即在js开始部分加上'use strict' 3.ff下需要知道测试版本,即在script标签的type属性中加上 'application/javascript:version=1.7' 属性值 二.区别 1.声明变量 var 可以多次声明同一个属性 let 需要在严格模式下才能用,不允许重复声明,没有与解析过程(声明之前调用不是undefin

ECMAscript

ECMAScript 1. ECMAScript ECMAScript:JavaScript的规范 ECMA-262号文件 - JavaScript 循环的方法: var ary = [1,2,3,4,5]; // for循环 for(var i = 0;i < ary.length;i++){ console.log(ary[i]); } // forEach ary.forEach(function(value){ console.log(value); }) // for..in.. fo

Javascript与ECMAScript

我们经常习惯性认为Javascript就是ECMAScript,但其实不是这样的. ECMAScript是一种脚本在语法和语义上的标准. 主要包括:语法.类型.语句.关键字.保留字.操作符.对象. 它与浏览器之间,没有半毛钱关系. 而Javascript是基于ECMAScript标准实现的.Javascript不仅仅包括ECMAScript,它其实还包含了其他东西. Javascript主要由三个部分组成,见下图: 在上面ECMAScript与Javascript的比较中,已经谈了ECMAScr

[Node.js] ECMAScript 6中的生成器及koa小析

原文地址:http://www.moye.me/2014/11/10/ecmascript-6-generator/ 引子 老听人说 koa大法好,这两天我也赶了把时髦:用 n 安上了node 0.11.12,下了个koa开启harmony模式试水.在一系列文档和贴子的教育下,大概认识到: koa 是TJ大神主导的新一代Web框架 koa 的中间件基于ES6的生成器函数(function *)形式 koa的核心流程库是 co,它能很好的解决Pyramid of Doom问题 在接触 Node.j

ECMAScript prototype的一个疑问。

既然是疑问 当然首先要贴一段代码. 背景: 探究js的原型继承模式. 疑惑:为何person1和person2的prototype 居然是相等的. 附: 1.Object.create(proto, [ propertiesObject ]) 参数 proto 一个对象,作为新创建对象的原型.或者为 null. propertiesObject 可选.该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符(这些属性描述符的结构与Object.defineProper