JavaScript中创建原子的方法总结

前言

原子操作这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。

当然JS是单线程的,所以不存在线程打断这么一说,我只是从Java中借引了这么一个概念。如果一段JS代码在执行过程中没有未知操作被引入,那么这段代码就是100%可控和安全的,这就是原子操作。反之非原子操作可能会因为外界操作的引入导致代码变得难以控制而产生隐晦的bug。

JavaScript中可以通过Object.create(null)来创建原子,这是非常自然而又易于理解的方式。不过也有一些其它的方法来实现相同的效果,虽然在概念上有所不同,但是它们创建的一样是“原子对象”。

创建原子

使用Object.create()

// 方法1
atom = Object.create(null)

使用Object.setPrototypeOf()

// 方法2
atom = Object.setPrototypeOf(new Object, null)

// OR
atom = Object.setPrototypeOf({}, null)

重置构造器的原型属性

// 方法3
function MyObject() {
 // ...
}
Object.setPrototypeOf(MyObject.prototype, null);

atom = new MyObject;

重置类的原型

注:“非派生类(没有extends声明的类)”,与将一个普通函数用作构造器时的特性基本一致。

class MyClass {
 // ...
}
Object.setPrototypeOf(MyClass.prototype, null);

atom = new MyClass;

使用派生自null值的类

JavaScript在处理extends null时会将MyClass.prototype的原型置为null,因此这个类构建的实例自然就是atom。但是,派生自null值的类无法直接构建,因此需要声明自己的构造方法(以该方法创建和返回的对象作为this)。

// 方法4
class MyClass extends null {
 constructor() {
 return Object.create(new.target.prototype);
 }
}
atom = new MyClass;

上例在实现构造方法constructor()时是直接引用new.target.prototype来作为原型的,这样也就可以在new运算时引用到MyClass子类的原型。例如:

// 方法5
class MyClassEx extends MyClass {
 get description() {
 return 'class MyClassEx';
 }
}
atom = new MyClassEx;
console.log(atom.description); // class MyClassEx

使用一般函数并直接返回原子

下面的代码是兼容构造器、原型继承和函数调用等方式的。

// 方法6
// (当作为函数调用时,new.target为undefined值)
function MyAtom() {
 return Object.create(new.target && new.target.prototype || null);
}

// 示例1
atom = new MyAtom;
// 示例2
atom = MyAtom();

使用类来创建原子的一个特例

在上述方法4中,由于声明了extends null,因此类MyClass必须拥有一个自己的构造方法。但事实上在JavaScript中,extends null所表达的含义是:

  • 因为有extends声明,所以默认的constructor()将总是调用父类super()来创建实例(亦即是所谓“this引用总是由祖先类创建的”);但是,
  • 由于extends null意味着父类为null,因此“调用父类super() ”失败。

这是类MyClass不能使用默认的constructor() ——而“通常”必须由用户代码来实现构造方法的原因。然而JavaScript只是在静态语法分析时才通过extends null来识别父类,真正在运行期时,它是通过方法的内部槽([[HomeObject]])来动态查找super的。——由于该内部槽指向类MyClass(或类的原型属性MyClass.prototype),因此所谓的super其实就是如下的运算值:

// for static class methods
_super = Object.getPrototypeOf(MyClass)

// for instance methods
_super = Object.getPrototypeOf(MyClass.prototype)

既然如此,我们就可以通过如下的代码来声明一个“可以创建原子”的类。例如:

// 方法7
Atom = Object.setPrototypeOf(class extends null {}, Object)
atom = new Atom;

在这个方法中,实际上Atom指向类表达式,并且重置了它的原型:

// (如下等价于方法7的第一行代码)
Atom = class extends null {};
Object.setPrototypeOf(Atom.prototype, null);
Object.setPrototypeOf(Atom, Object);

其中extends null决定了Atom.prototype的原型指向一个null值,而setPrototypeOf(...)决定了当new Atom()时,对象实际上是由Object() ——这个super来创建的。因此,当new Atom时,实际发生的操作是:

// (如下等价于方法7的第二行代码)
_this = new Object(); // call super()
Object.setPrototypeOf(_this, Atom.prototype); // prototype is null
atom = _this;

所以方法7是一种能够“利用JavaScript原生构造器”来创建原子的技巧。比如最简单的获得一个Arguments()构造器的方法其实是这样:

let Arguments = Object.setPrototypeOf(class extends null {}, Array);

这样得到的对象将与JavaScript内置在函数调用中创建的argument完全一致:

// (JavaScript的arguments对象也是原子)
let arguments = new Arguments(1,2,3); // more paraments

不过在ES6中arguments也实现了迭代器界面,因此需要一行额外的代码来处理之:

// for ES6
Arguments.prototype[Symbol.iterator] = [][Symbol.iterator];

其它

1. 关于映射类

在Metameta( @aimingoo/metameta (本地下载))中,可以使用Meta.from()来得到一个映射类,这与上面的方法7是相同的方式:

// 方法8(in metameta)
MyAtomObject = Meta.from(Object);
atom = new MyAtomObject;

有趣的是,这样得到的“MyAtomObject类”(在Metameta中称为Objext类)将继承所有来自Object.xxx的类方法,例如Object.keys()等。这些方法在元系统中也是可以直接使用的。例如:

// (in metameta)
Objext = Meta.from(Object);
Objext.keys(new Objext);

2. 关于extends new.target

上面在方法4中提到extends null相当于将MyClass.prototype的原型设为null,——在方法7中也使用了这一技巧——因此事实上在Metameta( @aimingoo/metameta (本地下载))中实现MetaMeta()时,采用的extends new.target也就相当于:

// class MyClass extends new.target ...
Object.setPrototypeOf(MyClass.prototype, new.target)

这一技巧用在类的constructor()方法中,返回一个新的类(类声明的表达式)。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • JavaScript中创建原子的方法总结

    前言 原子操作这是Java多线程编程的老生常谈了.所谓原子操作是指不会被线程调度机制打断的操作:这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程). 当然JS是单线程的,所以不存在线程打断这么一说,我只是从Java中借引了这么一个概念.如果一段JS代码在执行过程中没有未知操作被引入,那么这段代码就是100%可控和安全的,这就是原子操作.反之非原子操作可能会因为外界操作的引入导致代码变得难以控制而产生隐晦的bug. JavaScript中可以通过

  • JavaScript中 创建动态 QML 对象的方法

    一.动态创建对象 有两种方法可以从 JavaScript 动态创建对象: 调用 Qt.createComponent() 动态创建 Component 对象 使用 Qt.createQmlObject() 从 QML 字符串创建对象 虽然动态创建的对象可以与其他对象一样使用,但它们在 QML 中没有 id. 1.1.动态创建组件 可以调用它的 createObject() 方法来创建该组件的一个实例.这个函数可以接受两个参数: 第一个是新对象的父对象.父对象可以是图形对象(即 Item 类型)或

  • JavaScript中setter和getter方法介绍

    javascript中的setter.getter是平时接触比较少的方法,其本身也并不是标准方法,只在非ie浏览器里支持(ie内核也许有其他方法可以做到呢?暂时不知其解),但是加以利用可以做许多事情,比如: 1.对数据的访问限制: a.value是对value变量的getter方法调用,如果在getter方法实现中抛出异常,可以阻止对value变量的访问 2.对dom变量进行监听: window.name是一个跨域非常好用的dom属性(大名鼎鼎,详见百度),如果覆盖window.name的set

  • 深入理解关于javascript中apply()和call()方法的区别

    如果没接触过动态语言,以编译型语言的思维方式去理解javaScript将会有种神奇而怪异的感觉,因为意识上往往不可能的事偏偏就发生了,甚至觉得不可理喻.如果在学JavaScript这自由而变幻无穷的语言过程中遇到这种感觉,那么就从现在形始,请放下的您的"偏见",因为这对您来说绝对是一片新大陆,让JavaScrip慢慢融化以前一套凝固的编程意识,注入新的生机! 好,言归正传,先理解JavaScrtipt动态变换运行时上下文特性,这种特性主要就体现在apply, call两个方法的运用上.

  • JavaScript 批量创建数组的方法

    JavaScript有许多批量创建数组的方法,为了衡量它们的性能,我分别使用不同的方法创建一个长度为100000的数组,且键和值相等.同时,我定义了下面这个函数用来测量创建数组所耗费的时间: function t(fn) { var start = Date.now(); fn.call(this); var end = Date.now(); return (end - start) + 'ms'; } 以下为常用的几个创建数组的方法以及它们所耗费的时间: 使用join和split 该方法把大

  • JavaScript 中使用 Generator的方法

    Generator 是一种非常强力的语法,但它的使用并不广泛(参见下图 twitter 上的调查!).为什么这样呢?相比于 async/await,它的使用更复杂,调试起来也不太容易(大多数情况又回到了从前),即使我们可以通过非常简单的方式获得类似体验,但是人们一般会更喜欢 async/await. 然而,Generator 允许我们通过 yield 关键字遍历我们自己的代码!这是一种超级强大的语法,实际上,我们可以操纵执行过程!从不太明显的取消操作开始,让我们先从同步操作开始吧. 我为文中提到

  • JavaScript中call和apply方法的区别实例分析

    本文实例分析了JavaScript中call和apply方法的区别.分享给大家供大家参考,具体如下: 这两个方法不经常用,但是在某些特殊场合中是非常有用的,下面主要说下它们的区别: 1.首先,JavaScript是一门面向对象的语言,也就是说它有this的概念.而且JavaScript是一门动态类型语言,为什么说它是动态类型语言呢?因为JavaScript在编译时没有类型检查的过程,不会去检查创建的对象类型,也不会去检查传递的参数类型,所以它的变量类型在运行期间是可以改变的. 2.要知道call

  • JavaScript 中创建私有成员

    目录 1.使用闭包 2.使用 ES6 类 3.使用 ES2020 提案 4.使用 WeakMap 5.使用 TypeScript 前言: 面向对象编程语言中的 private 关键字是一个访问修饰符,可用于使属性和方法只能在声明的类中访问.这使得隐藏底层逻辑变得容易,这些底层逻辑应该被隐藏起来,并且不应该与类的外部交互. 但是如何在 JavaScript 中实现类似的功能呢? 没有保留关键字 private ,但在新的标准中 JavaScript 有自己的方法来创建类私有成员,但目前还处于 ES

  • JavaScript中find()和 filter()方法的区别小结

    目录 前言 JavaScript find() 方法 JavaScript filter() 方法 find() 和 filter() 的区别与共点 直接上代码 总结 前言 JavaScript 在 ES6 上有很多数组方法,每种方法都有独特的用途和好处. 在开发应用程序时,大多使用数组方法来获取特定的值列表并获取单个或多个匹配项. 在列出这两种方法的区别之前,我们先来一一了解这些方法. JavaScript find() 方法 ES6 find() 方法返回通过测试函数的第一个元素的值.如果没

  • JavaScript中Object.prototype.toString方法的原理

    在JavaScript中,想要判断某个对象值属于哪种内置类型,最靠谱的做法就是通过Object.prototype.toString方法. var arr = []; console.log(Object.prototype.toString.call(arr)) //"[object Array]" 本文要讲的就是,toString方法是如何做到这一点的,原理是什么. ECMAScript 3 在ES3中,Object.prototype.toString方法的规范如下: 15.2.

随机推荐