「译」一起探讨 JavaScript 的对象

「译」一起探讨 JavaScript 的对象


一起探讨 JavaScript 的对象

对象是多个属性的动态集合,它有一个链接着原型的隐藏属性(注:__proto__)。

一个属性拥有一个 key 和一个 value 。

属性的 key

属性的 key 是一个唯一的字符串。

访问属性有两种方式:点表示法和括号表示法。当使用点表示法,属性的 key 必须是有效的标识符。

let obj = {
  message : "A message"
}
obj.message //"A message"
obj["message"] //"A message"
复制代码

访问一个不存在的属性不会抛出错误,但是会返回 undefined

obj.otherProperty //undefined
复制代码

当使用括号表示法,属性的 key 不要求是有效的标识符 —— 可以是任意值。

let french = {};
french["thank you very much"] = "merci beaucoup";

french["thank you very much"]; //"merci beaucoup"
复制代码

当属性的key是一个非字符串的值,会用toString()方法(如果可用的话)把它转换为字符串。

let obj = {};
//Number
obj[1] = "Number 1";
obj[1] === obj["1"]; //true
//Object
let number1 = {
  toString : function() { return "1"; }
}
obj[number1] === obj["1"]; //true
复制代码

在上面的示例中,对象 number1 被用作一个 key 。它会被转换为字符串,转换结果 “1” 被用作属性的 key 。

属性的值

属性的值可以是任意的基础数据类型,对象,或函数。

对象作为值

对象可以嵌套在其他对象里。看下面这个例子

let book = {
  title : "The Good Parts",
  author : {
    firstName : "Douglas",
    lastName : "Crockford"
  }
}
book.author.firstName; //"Douglas"
复制代码

通过这种方式,我们就可以创建一个命名空间:

let app = {};
app.authorService = { getAuthors : function() {} };
app.bookService = { getBooks : function() {} };
复制代码

函数作为值

当一个函数被作为属性值,通常成为一个方法。在方法中,this 关键字代表着当前的对象。

this ,会根据函数的调用方式有不同的值。了解更多关于this 丢失上下文的问题,可以查看当"this"丢失上下文时应该怎么办

动态性

对象本质上就是动态的。可以任意添加删除属性。

let obj = {};
obj.message = "This is a message"; //add new property
obj.otherMessage = "A new message"; //add new property
delete obj.otherMessage; //delete property
复制代码

Map

我们可以把对象当做一个 Map。Map 的 key 就是对象的属性。

访问一个 key 不需要去扫描所有属性。访问的时间复杂度是 o(1)。

原型

对象有一个链接着原型对象的“隐藏”属性 __proto__,对象是从这个原型对象中继承属性的。

举个例子,使用对象字面量创建的对象有一个指向 Object.prototype 的链接:

var obj = {};
obj.__proto__ === Object.prototype; //true
复制代码

原型链

原型对象有它自己的原型。当一个属性被访问的时候并且不包含在当前对象中,JavaScript会沿着原型链向下查找直到找到被访问的属性,或者到达 null 为止。

只读

原型只用于读取值。对象进行更改时,只会作用到当前对象,不会影响对象的原型;就算原型上有同名的属性,也是如此。

空对象

正如我们看到的,空对象 {} 并不是真正意义上的空,因为它包含着指向 Object.prototype 的链接。为了创建一个真正的空对象,我们可以使用 Object.create(null) 。它会创建一个没有任何属性的对象。这通常用来创建一个Map。

原始值和包装对象

在允许访问属性这一点上,JavaScript 把原始值描述为对象。当然了,原始值并不是对象。

(1.23).toFixed(1); //"1.2"
"text".toUpperCase(); //"TEXT"
true.toString(); //"true"
复制代码

为了允许访问原始值的属性, JavaScript 创造了一个包装对象,然后销毁它。JavaScript引擎对创建包装和销毁包装对象的过程做了优化。

数值、字符串和布尔值都有等效的包装对象。跟别是:NumberStringBoolean

null 和 undefined 原始值没有相应的包装对象并且不提供任何方法。

内置原型

Numbers 继承自Number.prototypeNumber.prototype继承自Object.prototype

var no = 1;
no.__proto__ === Number.prototype; //true
no.__proto__.__proto__ === Object.prototype; //true
复制代码

Strings 继承自 String.prototype。Booleans 继承自 Boolean.prototype

函数都是对象,继承自 Function.prototype 。函数拥有 bind()apply() 和 call() 等方法。

所有对象、函数和原始值(除了 null 和 undefined )都从 Object.prototype 继承属性。他们都有 toString() 方法。

使用 polyfill 扩充内置对象

JavaScript 可以轻松地使用新功能扩充内置对象。

polyfill 就是一个代码片段,用于在不支持某功能的浏览器中实现该功能。

实用工具

举个例子,这个为 Object.assign() 写的polyfill,如果它不可用,那么就在 Object 上添加一个新方法。

为 Array.from() 写了类似的polyfill,如果它不可用,就在 Array 上添加一个新方法。

原型

新的方法可以被添加到原型。

举个例子,String.prototype.trim() polyfill让所有的字符串都能使用 trim() 方法。

let text = "   A text  ";
text.trim(); //"A text"
复制代码

Array.prototype.find() polyfill让所有的数组都能使用find()方法。polyfill也是同样的。

let arr = ["A", "B", "C", "D", "E"];
arr.indexOf("C"); //2
复制代码

单一继承

Object.create() 用特定的原型对象创建一个新对象。它用来做单一继承。思考下面的例子:

let bookPrototype = {
  getFullTitle : function(){
    return this.title + " by " + this.author;
  }
}
let book = Object.create(bookPrototype);
book.title = "JavaScript: The Good Parts";
book.author = "Douglas Crockford";
book.getFullTitle();//JavaScript: The Good Parts by Douglas Crockford
复制代码

多重继承

Object.assign() 从一个或多个对象拷贝属性到目标对象。它用来做多重继承。看下面的例子:

let authorDataService = { getAuthors : function() {} };
let bookDataService = { getBooks : function() {} };
let userDataService = { getUsers : function() {} };
let dataService = Object.assign({},
 authorDataService,
 bookDataService,
 userDataService
);
dataService.getAuthors();
dataService.getBooks();
dataService.getUsers();
复制代码

不可变对象

Object.freeze() 冻结一个对象。属性不能被添加、删除、更改。对象会变成不可变的。

"use strict";
let book = Object.freeze({
  title : "Functional-Light JavaScript",
  author : "Kyle Simpson"
});
book.title = "Other title";//Cannot assign to read only property ‘title‘
复制代码

Object.freeze() 实行浅冻结。要深冻结,需要递归冻结对象的每一个属性。

拷贝

Object.assign() 被用作拷贝对象。

let book = Object.freeze({
  title : "JavaScript Allongé",
  author : "Reginald Braithwaite"
});
let clone = Object.assign({}, book);
复制代码

Object.assign() 执行浅拷贝,不是深拷贝。它拷贝对象的第一层属性。嵌套的对象会在原始对象和副本对象之间共享。

对象字面量

对象字面量提供一种简单、优雅的方式创建对象。

let timer = {
  fn : null,
  start : function(callback) { this.fn = callback; },
  stop : function() {},
}
复制代码

但是,这种语法有一些缺点。所有的属性都是公共的,方法能够被重定义,并且不能在新实例中使用相同的方法。

timer.fn;//null
timer.start = function() { console.log("New implementation"); }
复制代码

Object.create()

Object.create() 和 Object.freeze() 一起能够解决最后两个问题。

首先,我要使用所有方法创建一个冻结原型 timerPrototype ,然后创建对象去继承它。

let timerPrototype = Object.freeze({
  start : function() {},
  stop : function() {}
});
let timer = Object.create(timerPrototype);
timer.__proto__ === timerPrototype; //true
复制代码

当原型被冻结,继承它的对象不能够更改其中的属性。现在,start() 和 stop() 方法不能被重新定义。

"use strict";
timer.start = function() { console.log("New implementation"); } //Cannot assign to read only property ‘start‘ of object
复制代码

Object.create(timerPrototype) 可以用来使用相同的原型构建更多对象。

构造函数

最初,JavaScript 语言提出构造函数作为这些的语法糖。看下面的代码:

function Timer(callback){
  this.fn = callback;
}
Timer.prototype = {
  start : function() {},
  stop : function() {}
}
function getTodos() {}
let timer = new Timer(getTodos);
复制代码

所有的以 function 关键字定义的函数都可以作为构造函数。构造函数使用功能 new 调用。新对象将原型设定为 FunctionConstructor.prototype

let timer = new Timer();
timer.__proto__ === Timer.prototype;
复制代码

同样地,我们需要冻结原型来防止方法被重定义。

Timer.prototype = Object.freeze({
  start : function() {},
  stop : function() {}
});
复制代码

new 操作符

当执行 new Timer() 时,它与函数 newTimer() 作用相同:

function newTimer(){
  let newObj = Object.create(Timer.prototype);
  let returnObj = Timer.call(newObj, arguments);
  if(returnObj) return returnObj;

  return newObj;
}
复制代码

使用 Timer.prototype 作为原型,创造了一个新对象。然后执行 Timer 函数并为新对象设置属性字段。

ES2015为这一切带来了更好的语法糖。看下面的例子:

class Timer{
  constructor(callback){
    this.fn = callback;
  }

  start() {}
  stop() {}
}
Object.freeze(Timer.prototype);
复制代码

使用 class 构建的对象将原型设置为 ClassName.prototype 。在使用类创建对象时,必须使用 new 操作符。

let timer= new Timer();
timer.__proto__ === Timer.prototype;
复制代码

class 语法不会冻结原型,所以我们需要在之后进行操作。

Object.freeze(Timer.prototype);
复制代码

基于原型的继承

在 JavaScript 中,对象继承自对象。

构造函数和类都是用来创建原型对象的所有方法的语法糖。然后它创建一个继承自原型对象的新对象,并为新对象设置数据字段 基于原型的继承具有保护记忆的好处。原型只创建一次并且由所有的实例使用。

没有封装

基于原型的继承模式没有私有性。所有对象的属性都是公有的。

Object.keys() 返回一个包含所有属性键的数组。它可以用来迭代对象的所有属性。

function logProperty(name){
  console.log(name); //property name
  console.log(obj[name]); //property value
}
Object.keys(obj).forEach(logProperty);
复制代码

模拟的私有模式包含使用 _ 来标记私有属性,这样其他人会避免使用他们:

class Timer{
  constructor(callback){
    this._fn = callback;
    this._timerId = 0;
  }
}
复制代码

工厂模式

JavaScript 提供一种使用工厂模式创建封装对象的新方式。

function TodoStore(callback){
    let fn = callback;

    function start() {},
    function stop() {}

    return Object.freeze({
       start,
       stop
    });
}
复制代码

fn 变量是私有的。只有 start() 和 stop() 方法是公有的。start() 和 stop() 方法不能被外界改变。这里没有使用 this ,所以没有 this 丢失上下文的问题。

对象字面量依然用于返回对象,但是这次它只包含函数。更重要的是,这些函数是共享相同私有状态的闭包。 Object.freeze() 被用来冻结公有 API。

Timer 对象的完整实现,请看具有封装功能的实用JavaScript对象.

结论

JavaScript 像对象一样处理原始值、对象和函数。

对象本质上是动态的,可以用作 Map。

对象继承自其他对象。构造函数和类是创建从其他原型对象继承的对象的语法糖。

Object.create() 可以用来单一继承,Object.assign() 用来多重继承。

工厂函数可以构建封装对象。

有关 JavaScript 功能的更多信息,请看:

Discover the power of first class functions

How point-free composition will make you a better functional programmer

Here are a few function decorators you can write from scratch

Why you should give the Closure function another chance

Make your code easier to read with Functional Programming

原文地址:https://www.cnblogs.com/kuruma/p/9573914.html

时间: 2024-10-29 19:07:27

「译」一起探讨 JavaScript 的对象的相关文章

「译」JavaScript 的怪癖 1:隐式类型转换

原文:JavaScript quirk 1: implicit conversion of values 译文:「译」JavaScript 的怪癖 1:隐式类型转换 译者:justjavac 零:提要 [此贴子是 javascript 的 12 个怪癖(quirks) 系列的第一篇.] JavaScript 是非常宽容的,「来者不拒」,不在乎什么类型. 例如,它如果想要接受数字,它并不拒绝其他类型的值,而是试图把它们转换成数字: > '5' - '2' 3 > '5' * '2' 10 自动转

「译」forEach循环中你不知道的3件事

前言 本文925字,阅读大约需要7分钟. 总括: forEach循环中你不知道的3件事. 原文地址:3 things you didn't know about the forEach loop in JS 公众号:「前端进阶学习」,回复「666」,获取一揽子前端技术书籍 自弃者扶不起,自强者击不倒. 正文 你觉得你真的学会用forEach了么? 这是我之前对forEach循环的理解:就是一个普通语义化之后的for循环,可以被break,continue,return. 这篇文章将向你展示for

「译」如何正确学习JavaScript

原文:How to Learn JavaScript Properly 目录 不要这样学习JavaScript 本课程资源 1-2周(简介,数据类型,表达式和操作符) 3~4周(对象,数组,函数,DOM,jQuery) JavaScript终极编辑器:WebStorm 第一个项目-动态问答应用 5-6周(正则表达式,Window对象,事件,jQuery) 7周,可延长到8周(类,继承,HTML5) 继续提升 一些鼓励的话 上面的课程大纲提供了一个结构化和富有启发性的学习线路,从初学者到有所建树,

【转】「译」ES5, ES6, ES2016, ES.Next: JavaScript 的版本是怎么回事?

ES5, ES6, ES2016, ES.Next: What's going on with JavaScript versioning? Posted by Hux on September 22, 2015 JavaScript 有着很奇怪的命名史. 1995 年,它作为网景浏览器(Netscape Navigator)的一部分首次发布,网景给这个新语言命名为 LiveScript.一年后,为了搭上当时媒体热炒 Java 的顺风车,临时改名为了 JavaScript (当然,Java 和

Java的参数传递是「按值传递」还是「按引用传递」?

Java 编程语言中最大的困惑之一就是: java 是按值传递还是按引用传递.我在面试中经常会问面试者这个问题,但还是有很多面试者对这个问题的理解不是很正确. 有很多面试者是这样理解的: 如果传递类型为基础数据类型,则按值传递, 如果传递类型为类,则按引用传递. 这样的理解正确吗?他们甚至还可以写出示例代码来验证他们的想法,让我们来一起看一看大多数人是如何验证"基础类型按值传递,非基础类型按引用传递"这个想法的: 基础类型数据作为参数传递 /** * 基础类型数据作为参数传递 * @A

JavaScript 引擎「V8」发布 8.0 版本,内存占用量大幅下降

上周,JavaScript 引擎「V8」的开发团队在该项目官方网站上正式宣布推出最新的 8.0 版本.这次更新的重点主要集中在错误修复及性能改善上,正式的版本将在数周后随着谷歌 Chrome 80 稳定版一起发布. V8 是谷歌公司推出的开源高性能 JavaScript 引擎,主要用于提升 Web 浏览器内部 JavaScript 脚本执行的性能.V8 通过 C++ 语言编写,主要用在 Chrome 浏览器以及 Node.js 上,实现了对 ECMAScript 与 WebAssembly 的支

「微信同声传译」小程序插件:快速实现语音转文字、文本翻译、语音合成等能力

上期,我们在<「医院 LBS 位置服务」插件:输出LBS室内位置能力,为改善就医服务提供解决方案>一文中介绍了「医院 LBS 位置服务」小程序插件的意义.使用场景以及使用方法. 今天我们为大家推荐的插件是「微信同声传译」,如果你想在小程序中拥有快速实现语音转文字.文本翻译.语音合成等能力,那么「微信同声传译插件」是你的不二选择.接下来,我们将从使用场景到使用方法,为你作出详细的介绍. 「微信同声传译」插件能做什么? 今天我们说到的「微信同声传译」插件,是由微信智聆语音团队.微信翻译团队与公众平

大数据和「数据挖掘」是何关系?---来自知乎

知乎用户,互联网 244 人赞同 在我读数据挖掘方向研究生的时候:如果要描述数据量非常大,我们用Massive Data(海量数据)如果要描述数据非常多样,我们用Heterogeneous Data(异构数据)如果要描述数据既多样,又量大,我们用Massive Heterogeneous Data(海量异构数据)--如果要申请基金忽悠一笔钱,我们用Big Data(大数据) 编辑于 2014-02-2817 条评论感谢 收藏没有帮助举报作者保留权利 刘知远,NLPer 4 人赞同 我觉得 大数据

从「集装箱」思考Docker风潮

从「集装箱」思考Docker风潮 -- Docker潮流下的赢家策略 By 高焕堂 (台灣Docker聯盟 主席) 2015/02/20 前言 在许多革命性转折里,经常出现集装箱的身影:它就像幸运草一般,总是带来许多幸福和财运.现在Docker风起云涌,再现集装箱身影,如果开放视野.大力支持它,持续发挥它的潜能和力量,则幸运草就会出现在我们身旁了. 由于Docker集装箱带来的商机,其最直接的受益者是软件管理者(或称维运者),例如软件测试工具业者.测试人员等.因此在今天,不论您是开发者或是维运者