Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

什么 JS 中的对象字面量很酷 #214

Open
husky-dot opened this issue Apr 2, 2020 · 0 comments
Open

什么 JS 中的对象字面量很酷 #214

husky-dot opened this issue Apr 2, 2020 · 0 comments

Comments

@husky-dot
Copy link
Owner

作者:Dmitri Pavlutin
译者:前端小智
来源:dmitripavlutin

点赞再看,养成习惯

本文 GitHub https://github.com/qq449245884/xiaozhi 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

在 ES6 之前,JS 中的对象字面量(也称为对象初始化器)是非常基础的。可以定义两种类型的属性:

  • 键值对 {name1: value1}

  • 获取器 { get name(){..} }设置器 { set name(val){..}} 的计算属性值

    var myObject = {
    myString: 'value 1',
    get myNumber() {
    return this._myNumber;
    },
    set myNumber(value) {
    this._myNumber = Number(value);
    },
    };
    myObject.myString; // => 'value 1'
    myObject.myNumber = '15';
    myObject.myNumber; // => 15

JS 是一种基于原型的语言,因此一切都是对象。 在对象创建,配置和访问原型时,必须提供一种易于构造的语言。

定义一个对象并设置它的原型是一个常见的任务。最好的方式是直接在对象字面量使用一条语句来设置原型。

不幸的是,字面量的局限性不允许用一个简单的解决方案来实现这一点。必须结合使用object.create() 和对象字面量来设置原型。

var myProto = {
  propertyExists: function(name) {
    return name in this;
  }
};

var myNumbers = Object.create(myProto);
myNumbers['arrat'] = [1, 6, 7];
myNumbers.propertyExists('array'); // => true
myNumbers.propertyExists('collection'); // => false

我认为这种解决方案不够灵活。JS 是基于原型的,为什么要用原型创建对象那么麻烦?

幸运的是,JS 也在慢慢完善。JS 中很多令人沮丧的问题都是逐步解决的。

本文演示了 ES 6 如何解决上述问题,并使用额外的功能改进对象字面量。

  • 在对象构造上设置原型
  • 方法的声明
  • super 调用
  • 计算属性名

1. 在对象构造上设置原型

如你所知,访问现有对象原型的一种方法是使用 getter 属性 __proto__

var myObject = {
  name: 'Hello World!',
};
myObject.__proto__; // => {}
myObject.__proto__.isPrototypeOf(myObject); // => true

myObject.__ proto__ 返回 myObject 的原型对象。

请注意,不建议将 object.__ proto__ 用作 getter/setter。替代方法应考虑使用Object.getPrototypeOf()Object.setPrototypeOf()

ES6允许使用__proto__作为属性名,并在 {__proto__:protoObject}中设置原型。

接着,咱们使用 __proto__ 属性进行对象初始化,并优化上面的代码:

var myProto = {
  propertyExists: function(name) {
    return name in this;
  },
};
var myNumbers = {
  __proto__: myProto,
  array: [1, 6, 7],
};
myNumbers.propertyExists('array'); // => true
myNumbers.propertyExists('collection'); // => false

myNumbers 对象是使用特殊属性名 proto 与创建原型 myProto,这次咱们使用一条语句就创建,没有像上面还需要 object.create() 这样的附加函数。

如你所看,使用 __proto__ 进行编码很简单,我一直喜欢简单明了的解决方案。

说点脱离主题。 我觉得奇怪的是,简单灵活的解决方案需要大量的工作和设计。如果解决方案很简单,你可能会认为设计起来很容易。但是反之亦然:

  • 要使它简单明了是很复杂的
  • 把它变得复杂和难以理解是很容易的

如果某些东西看起来太复杂或难以使用,则可能还需要进一步的完善。

你对简单性有何看法? (请在下面随意写评论)

2.1 __proto__用法的特殊情况

即使__proto__看起来很简单,您也应该注意一些特殊情况。

在对象字面量中只能使用__proto__一次,否则 JS 会报错:

var object = {
  __proto__: {
    toString: function() {
      return '[object Numbers]'
    }
  },
  numbers: [1, 5, 89],
  __proto__: {
    toString: function() {
      return '[object ArrayOfNumbers]'
    }
  }
};

上面示例中的对象字面量中使用两次__proto__属性,这是不允许的。在这种情况下,将在会抛出错误: SyntaxError: Duplicate __proto__ fields are not allowed in object literals

JS 约束只能用一个对象或 null 作为 __proto__ 属性的值。 任何使用原始类型(字符串,数字,布尔值)或 undefined 类型都将被忽略,并且不会更改对象的原型。

var objUndefined = {
  __proto__: undefined,
};
Object.getPrototypeOf(objUndefined); // => {}
var objNumber = {
  __proto__: 15,
};
Object.getPrototypeOf(objNumber); // => {}

对象字面量使用 undefined 和 数字 15 来设置 __proto__ 值。 因为仅允许将对象或 null 用作原型,所以__proto__值将被忽略,但 objUndefinedobjNumber 仍具有其默认原型:纯 JS 对象 {}, 。

当然,尝试使用基本类型来设置对象的原型也会很奇怪。

当对象字面具有计算结果为'proto'的字符串时 {['__proto__']:protoObj },也要小心。 以这种方式创建的属性不会更改对象的原型,而只是使用键 '__proto__' 创建一个拥有的属性

简写方法定义

可以使用较短的语法在对象常量中声明方法,以省略 function 关键字和 冒号的方式。 这被称为简写方法定义

接着,咱们使用简写的方法来定义一些方法:

var collection = {
  items: [],
  add(item) {
    this.items.push(item);
  },
  get(index) {
    return this.items[index];
  },
};
collection.add(15);
collection.add(3);
collection.get(0); // => 15

一个很好的好处是,以这种方式声明的方法被命名为函数,这对于调试目的很有用。 从上面示例中执行 collection.add.name 会返回函数名称 “add”

3. super 的使用

JS 一个有趣的改进是使用 super 关键字作为从原型链访问继承的属性的能力。 看下面的例子:

var calc = {
  numbers: null,
  sumElements() {
    return this.numbers.reduce(function(a, b) {
      return a + b;
    });
  },
};
var numbers = {
  __proto__: calc,
  numbers: [4, 6, 7],
  sumElements() {
    if (this.numbers == null || this.numbers.length === 0) {
      return 0;
    }
    return super.sumElements();
  },
};
numbers.sumElements(); // => 17

calcnumbers 对象的原型。 在 numberssumElements方法中,可以使用 super关键字从原型访问方法:super.sumElements()

最终,super 是从对象原型链访问继承的属性的快捷方式。

在前面的示例中,可以尝试直接执行 calc.sumElements() 来调用原型,会报错。 然而,super.sumElements() 可以正确调用,因为它访问对象的原型链。并确保原型中的 sumElements() 方法使用 this.numbers 正确访问数组。

super 存在清楚地表明继承的属性将被使用。

3.1 super 使用限制

super 只能在对象字面量的简写方法定义内使用。

如果试图从普通方法声明{ name: function(){} } 访问它,JS 将抛出一个错误:

var calc = {
  numbers: null,
  sumElements() {
    return this.numbers.reduce(function(a, b) {
      return a + b;
    });
  },
};
var numbers = {
  __proto__: calc,
  numbers: [4, 6, 7],
  sumElements: function() {
    if (this.numbers == null || this.numbers.length === 0) {
      return 0;
    }
    return super.sumElements();
  },
};
// Throws SyntaxError: 'super' keyword unexpected here
numbers.sumElements();

方法 sumElements 被定义为一个属性: sumElements: function(){…}。因为super 只能在简写方法中使用,所以在这种情况下调用它会抛出 SyntaxError: 'super' keyword unexpected here

此限制在很大程度上不影响对象字面量的声明方式。 由于语法较短,因此通常最好使用简写方法定义。

4.计算属性名

在 ES6 之前,对象初始化使用的是字面量的形式,通常是静态字符串。 要创建具有计算名称的属性,就必须使用属性访问器。

function prefix(prefStr, name) {
  return prefStr + '_' + name;
}
var object = {};
object[prefix('number', 'pi')] = 3.14;
object[prefix('bool', 'false')] = false;
object; // => { number_pi: 3.14, bool_false: false }

当然,这种定义属性的方式是令人愉快的。

接着使用简写方式来改完上面的例子:

function prefix(prefStr, name) {
  return prefStr + '_' + name;
}
var object = {
  [prefix('number', 'pi')]: 3.14,
  [prefix('bool', 'false')]: false,
};
object; // => { number_pi: 3.14, bool_false: false }

[prefix('number','pi')]通过计算 prefix('number', 'pi') 表达式(即'number_pi')来设置属性名称。

相应地,[prefix('bool', 'false')] 将第二个属性名称设置为'bool_false'

4.1 symbol 作为属性名称

symbol 也可以用作计算的属性名称。 只要确保将它们包括在方括号中即可:{[Symbol('name')]:'Prop value'}

例如,用特殊属性 Symbol.iterator 并迭代对象自身的属性名称。 如下示例所示:

var object = {
   number1: 14,
   number2: 15,
   string1: 'hello',
   string2: 'world',
   [Symbol.iterator]: function *() {
     var own = Object.getOwnPropertyNames(this),
       prop;
     while(prop = own.pop()) {
       yield prop;
     }
   }
}
[...object]; // => ['number1', 'number2', 'string1', 'string2']

[Symbol.iterator]: function *() { } 定义一个属性,该属性用于迭代对象的自有属性。 展开运算符 [... object] 使用迭代器并返回自有的属性的列表

剩余和展开属性

剩余属性允许从对象中收集在分配销毁后剩下的属性。

下面的示例在解构对象之后收集剩余的属性:

var object = {
  propA: 1,
  propB: 2,
  propC: 3,
};
let { propA, ...restObject } = object;
propA; // => 1
restObject; // => { propB: 2, propC: 3 }

展开属性允许将源对象的自有属性复制到对象文字面量中。 在此示例中,对象字面量从源对象收集到对象的其他属性:

var source = {
  propB: 2,
  propC: 3,
};
var object = {
  propA: 1,
  ...source,
};
object; // => { propA: 1, propB: 2, propC: 3 }

6.总结

在 ES6 中,即使是作为对象字面量的相对较小的结构也得到了相当大的改进。

可以使用__proto__ 属性名称直接从初始化器设置对象的原型。 这比使用 Object.create() 更容易。

请注意,__proto__ 是 ES6 标准附件B的一部分,不鼓励使用。 该附件实现对于浏览器是必需的,但对于其他环境是可选的。NodeJS 4、5和6支持此功能。

现在方法声明的形式更短,因此不必输入 function 关键字。 在简化方法中,可以使用 super关 键字,该关键字可以轻松访问对象原型链中的继承属性。

如果属性名称是在运行时计算的,那么现在您可以使用计算的属性名称[expression]来初始化对象。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://dmitripavlutin.com/why-object-literals-in-javascript-are-cool/


交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant