你的 mixin 真的兼容 ECMAScript 5 吗?


Posted in Javascript onApril 11, 2013

我最近在与客户合作的项目中,需要充分利用的 ECMAScript 5,在此我遇到一个非常有趣的问题。 该问题源于一个非常常见的模式: mixin , 也就是在 JavaScript 中把一个对象的属性或者方法 mixin 到另一个。

大多数 mixin 的功能看起来像这样:

function mixin(receiver, supplier) {
    for (var property in supplier) {
        if (supplier.hasOwnProperty(property)) {
            receiver[property] = supplier[property];
        }
    }
}

在此 mixin() 函数中,一个 for 循环遍历 supplier 对象的属性并赋值给 receiver 对象。 几乎所有的 JavaScript 库有某种形式的类似功能,让您可以编写这样的代码:

mixin(object, {
    name: "Nicholas",
    sayName: function() {
        console.log(this.name);
    }
});
object.sayName();       // outputs "Nicholas"

在此示例中,object 对象接收了属性 name 和方法 sayName()。 这在 ECMAScript 3 中运行良好,但在 ECMAScript 5 上却没那么乐观。

这是我遇到的问题:

(function() {
    // to be filled in later
    var name;
    mixin(object, {
        get name() {
            return name;
        }
    });
    // let's just say this is later
    name = "Nicholas";
}());
console.log(object.name);       // undefined

这个例子看起来有点做作,但它准确的描述这个问题。 进行 mixin 的属性使用了 ECMAScript 5 的新特性:一个 getter 属性存取器。 getter 引用一个未初始化的局部变量 name,因此这个属性未定义 undefined。

后来,name 被分配了一个值,以便使存取器 getter 可以返回一个有效的值。 不幸的是,object.name(被 mixin 的属性)始终返回 undefined。

这是怎么回事呢?

我们仔细分析 mixin() 函数。 事实上,在循环语句中,并没有把属性从一个对象重新赋值给到另一个对象。 它实际上是创建一个同名的属性,并把 supplier 对象的存取器方法 getter 的返回值赋值给了它。 (译注:目标对象得到的不是 getter 这个方法,而是得到了 getter 方法的返回值。@justjavac)

在这个例子中,mixin() 的过程其实是这样的:

receiver.name = supplier.name;

属性 receiver.name 被创建,并且被赋值为 supplier.name 的值。 当然,supplier.name 有一个 getter 方法用来返回本地变量 name 的值。 此时,name 的值为 undefined,所以 receiver.name 存储的是 值。 并没有为 receiver.name 创建一个 getter 方法,因此它的值永远不会改变。

要解决这个问题,你需要使用属性描述符(译注:descriptor)将属性从一个对象 mixin 到另一个对象。 一个纯粹的 ECMAScript 5 版本的 mixin() 应该这样写:

function mixin(receiver, supplier) {
    Object.keys(supplier).forEach(function(value, property) {
        Object.defineProperty(receiver, property, Object.getOwnPropertyDescriptor(supplier, property));
    });
}

在这个新版本函数中,Object.keys() 用来获取一个数组,包含了 supplier 对象的所有枚举属性。 然后,foreach() 方法用来遍历这些属性。 调用 Object.getOwnPropertyDescriptor() 方法获取 supplier 对象的每个属性描述符(descriptor)。

由于描述符(descriptor)包含了所有的属性信息,包括 getter 和 setter 方法, 该描述符(descriptor)可以直接传递给 Object.defineProperty() ,用来在 receiver 对象上创建相同的属性。 使用这个新版本的 mixin() ,可以解决前面遇到的问题,从而得到你所期望的结果。 getter 方法被正确地从 supplier 传递到了 receiver。

当然,如果你仍然需要支持旧的浏览器,那么你就需要一个函数,回落的 ECMAScript 3:

function mixin(receiver, supplier) {
    if (Object.keys) {
        Object.keys(supplier).forEach(function(value, property) {
            Object.defineProperty(receiver, property, Object.getOwnPropertyDescriptor(supplier, property));
        });
    } else {
        for (var property in supplier) {
            if (supplier.hasOwnProperty(property)) {
                receiver[property] = supplier[property];
            }
        }
    }
}

如果您需要使用一个 mixin() 函数,一定要仔细检查它在 ECMAScript 5 可以正常工作,特别是 getter 和 setter 方法。 否则,你会发现自己陷入像我一样的错误。

Javascript 相关文章推荐
jQuery 快速结束当前正在执行的动画
Nov 20 Javascript
JavaScript实现获取dom中class的方法
Feb 09 Javascript
JS+CSS实现大气的黑色首页导航菜单效果代码
Sep 10 Javascript
js调用屏幕宽度的简单方法
Nov 14 Javascript
微信小程序实现瀑布流布局与无限加载的方法详解
May 12 Javascript
bootstrap multiselect下拉列表功能
Aug 22 Javascript
webpack公共组件引用路径简化小技巧
Jun 15 Javascript
vue-cli2 构建速度优化的实现方法
Jan 08 Javascript
使用layer.msg 时间设置不起作用的解决方法
Sep 12 Javascript
关于引入vue.js 文件的知识点总结
Jan 28 Javascript
VUE table表格动态添加一列数据,新增的这些数据不可以编辑(v-model绑定的数据不能实时更新)
Apr 03 Javascript
vue设置全局访问接口API地址操作
Aug 14 Javascript
谈谈关于JavaScript 中的 MVC 模式
Apr 11 #Javascript
在JavaScript并非所有的一切都是对象
Apr 11 #Javascript
在JavaScript中typeof的用途介绍
Apr 11 #Javascript
浅谈关于JavaScript的语言特性分析
Apr 11 #Javascript
javascript中的delete使用详解
Apr 11 #Javascript
将字符串转换成gb2312或者utf-8编码的参数(js版)
Apr 10 #Javascript
原生js实现给指定元素的后面追加内容
Apr 10 #Javascript
You might like
Laravel 中获取上一篇和下一篇数据
2015/07/27 PHP
Zend Framework动作控制器用法示例
2016/12/09 PHP
php+redis实现多台服务器内网存储session并读取示例
2017/01/12 PHP
Yii框架响应组件用法实例分析
2019/09/04 PHP
JavaScript Event学习第五章 高级事件注册模型
2010/02/07 Javascript
读jQuery之四(优雅的迭代)
2011/06/20 Javascript
JQuery获取与设置HTML元素的内容或文本的实现代码
2014/06/20 Javascript
JS动态创建DOM元素的方法
2015/06/09 Javascript
js强制把网址设为默认首页
2015/09/29 Javascript
jQuery获取父元素节点、子元素节点及兄弟元素节点的方法
2016/04/14 Javascript
Ionic默认的Tabs模板使用实例
2016/08/29 Javascript
jqueryUI tab标签页代码分享
2017/10/09 jQuery
极简主义法编写JavaScript类
2017/11/02 Javascript
js 提取某()特殊字符串长度的实例
2017/12/06 Javascript
详解Vue取消eslint语法限制
2018/08/04 Javascript
微信小程序 数据缓存实现方法详解
2019/08/26 Javascript
js单线程的本质 Event Loop解析
2019/10/29 Javascript
[46:37]LGD vs TNC 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/16 DOTA
python定时执行指定函数的方法
2015/05/27 Python
Python3 Post登录并且保存cookie登录其他页面的方法
2018/12/28 Python
python儿童学游戏编程知识点总结
2019/06/03 Python
python实现输入任意一个大写字母生成金字塔的示例
2019/10/27 Python
python实现数据清洗(缺失值与异常值处理)
2019/12/02 Python
关于django python manage.py startapp 应用名出错异常原因解析
2020/12/15 Python
python解决OpenCV在读取显示图片的时候闪退的问题
2021/02/23 Python
css3 实现圆形旋转倒计时
2018/02/24 HTML / CSS
新西兰床上用品和家居用品购物网站:Adairs
2018/04/27 全球购物
伊莱克斯(Electrolux)俄罗斯网上商店:瑞典家用电器品牌
2021/01/23 全球购物
档案管理员岗位职责
2013/12/01 职场文书
学术会议欢迎词
2014/01/09 职场文书
早会主持词
2014/03/17 职场文书
勿忘国耻9.18演讲稿(经典篇)
2014/09/14 职场文书
小学主题班会教案
2015/08/17 职场文书
总结Python使用过程中的bug
2021/06/18 Python
JS的深浅复制详细
2021/10/16 Javascript
JavaScript原型链中函数和对象的理解
2022/06/16 Javascript