我是一个前端菜鸟,某一天,我发现了新大陆,我发现了 《ECMAScript 规范》 这个东西;javascript
头皮发麻~html
本文内容较多,请谨慎收看(我的笔记,我的理解都加在了上面,可能有些啰嗦);前端
我是一个菜鸡,理解有限,大佬轻喷。java
本文目的:数组
在没有探究 Object 以前,我是这样理解 Object 的:浏览器
- 数据的无序集合,里面包含 N 多个字段,每一个字段保存对应的信息,而对象就是这些个信息无序集合体,何为无序?
- 无序 故名思议:没有顺序的,有 “无序”,就存在 “有序”。
"有序","无序" 个人理解是:bash
- 数组是数据的有序集合,N 多个数据按照数组的索引井井有理的排列,咱们能够按照 “索引” 获取指定位置的数据。
- Object 而言,Object 内部字段是由一个个键值对构成的,键是字段的名字,值也就是该字段的数据;
- 在获取的时候,采用 Object[keyName] 的方式,也就获取到对应的字段存储的数据了。
- 不管字段在对象内部的位置,都是采用 Object.keyName 的方式获取的,故对象内部是没有顺序的。
// 一个叫 person 的 Object,里面有三个字段,包含不一样的数据,他们都属于 person 这个 Object
var person = {
name: "Jack",
age: 18,
sex: "man"
};
复制代码
ECMAScript 规范是全部 JavaScript 运行行为的权威来源;
不管是在你的浏览器环境、仍是在服务器环境( Node.js )、仍是在宇航服上[ NodeJS-NASA ]、或在你的物联网设备上[ JOHNNY-FIVE ];
全部 JavaScript 引擎的开发者都依赖于这个规范来确保他们各类天花乱坠的新特性可以其余 JavaScript 引擎同样,按预期工做。
ECMAScript 规范 毫不仅仅对 JavaScript 引擎开发者有用,
它对普通的 JavaScript 编码人员也很是有用,而你只是没有意识到或者没有用到。
Ps:你所用的 JavaScript,都是由这个规范来规定的,它说什么就是什么。
复制代码
在 ECMAScript 规范中是这样定义对象的:服务器
Object 是一个属性的集合。每一个属性既能够是一个命名的数据属性,也能够是一个命名的访问器属性,或是一个内部属性app
- 命名数据属性: 由一个名字与一个 ECMAScript 语言类型值和一个 Boolean 属性集合组成。
- 命名访问器属性: 由一个名字与一个或两个访问器函数,和一个 Boolean 属性集合组成。访问器函数用于存取一个与该属性相关联的 ECMAScript 语言类型 值。
- 内部属性: 没有名字,且不能直接经过 ECMAScript 语言操做。内部属性的存在纯粹为了规范的目的;
有两种带名字的访问器属性(非内部属性):get 和 put,分别对应取值和赋值。函数
规范就是规范,晦涩难懂。 😯
咱们已知 Object 是属性的集合。是否有过这个疑问,属性又是什么呢?或许规范已经给了咱们答案,告诉了咱们属性是怎么构成的。可是,我没明白。。。
好,咱们一步步分析下规范,回答下 属性是什么:
答案 1:属性是由一个名字与一个 ECMAScript 语言类型值和一个 Boolean 属性集合组成。
- 一个名字: 显而易见使咱们所熟知的 Key
- 一个 ECMAScript 语言类型值: 也就是值,语言类型值也就是指 String、Number、Blooean、Object、Function... 等等 JavaScript 的语言类型。
- 一个 Boolean 属性集合:??????????????
问题来了,什么是 Boolean 属性集合? 是否是历来没见过这个东西?
答案 2:由一个名字与一个或两个访问器函数,和一个 Boolean 属性集合组成。访问器函数用于存取一个与该属性相关联的 ECMAScript 语言类型 值。
- 一个名字: 相同的
- 一个或两个访问器函数:??????????????????
- 一个 Boolean 属性集合:??????????????
问题又来了,访问器函数? 听都没据说过? 更没见过? 这个答案也有 Boolean 属性集合?
再来看一下咱们平常是怎么使用 Object 的:
// 表达式建立一个对象
var busInfoForm = {
treeId: "",
vcName: "",
parentId: "0",
unitId: "",
iBindType: "",
bindId: ""
};
// 查 - 获取一个属性
console.log(busInfoForm.treeId);
// 改 - 设置一个属性
busInfoForm.vcName = "测试的值";
// 删 - 删除一个属性
delete busInfoForm.bindId;
// 增 - 增长一个属性
busInfoForm["obj"] = {
a: "this is obj a"
};
// 遍历属性
for (let k in busInfoForm) {
console.log(k, "-----", busInfoForm[k]);
}
复制代码
咱们对于数据的操做,无非 增删改查 定律,上面的例子体现了增删改查,以及枚举对象的属性操做. 咱们能够看到对象属性的 名字、值, 但咱们没有看到 Boolean 集合,也没有看到访问器函数。
这时候,咱们须要知道另外一个概念:属性描述符
在 MDN 中对于 属性描述符 的定义以下: 对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。
- 数据描述符 是一个具备值的属性,该值多是可写的,也可能不是可写的。
- 存取描述符 是由 getter-setter 函数对描述的属性。
- 描述符必须是这两种形式之一;不能同时是二者。
好吧,是否是懵了,属性描述符 又是什么?
继续看 MDN 上的对于属性描述符的介绍:
数据描述符和存取描述符均具备如下可选键值(默认值是在使用 Object.defineProperty()定义属性的状况下):
- configurable
- 当且仅当该属性的 configurable 为 true 时,该属性描述符才可以被改变,同时该属性也能从对应的对象上被删除。默认为 false。
- enumerable
- 当且仅当该属性的 enumerable 为 true 时,该属性才可以出如今对象的枚举属性中。默认为 false。
数据描述符同时具备如下可选键值:
- value
- 该属性对应的值。能够是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
- writable
- 当且仅当该属性的 writable 为 true 时,value 才能被赋值运算符改变。默认为 false。
存取描述符同时具备如下可选键值:
- get
- 一个给属性提供 getter 的方法,若是没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,可是会传入 this 对象(因为继承关系,这里的 this 并不必定是定义该属性的对象)。默认为 undefined。
- set
- 一个给属性提供 setter 的方法,若是没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受惟一参数,即该属性新的参数值。默认为 undefined。
configurable enumerable value writable get set 数据描述符 Yes Yes Yes Yes No No 存取描述符 Yes Yes No No Yes Yes 若是一个描述符不具备 value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。若是一个描述符同时有(value 或 writable)和(get 或 set)关键字,将会产生一个异常。
OK, 属性描述符的全部 "属性" 都罗列在了这里,白话一下:
- 对于对象中的每个属性,都有其本身的 “描述符” ,所谓的描述符,也就是用来描述属性的行为的,描述属性是否能够增删改查,以及如何增删改查。
- 描述符就像属性的辅助对象,这个对象和 Object 的属性结构同样由键值对构成,具体包含哪些键值对,以下所示:
- 数据描述符: 包含 configurable、enumerable、value、writable 键
- 存取描述符: 包含 configurable、enumerable、get、set 键
- 能够看到,configurable、enumerable 是都含有的。
- 而对于 数据描述符 而言,除了公有的,只能存在 value && writable,反过来 存取描述符 只能存在 get、set,这四个属性两两之间是不能共存的。
如今,咱们已经有了数据描述符的这个概念,下面来看下 Object.defineProperty() 这个方法
定义:
- Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
语法:
Object.defineProperty(obj, prop, descriptor)
参数:
- obj: 要在其上定义属性的对象
- prop:要定义或修改的属性的名称
- descriptor:将定义或修改的属性描述符
返回值
- 被传递给函数的对象,原对象
描述:
- 该方法容许精确添加或修改对象的属性。
- 经过赋值操做添加的普通属性是可枚举的,可以在属性枚举期间呈现出来(for...in 或 Object.keys 方法), 这些属性的值能够被改变,也能够被删除。
- 这个方法容许修改默认的额外选项(或配置)。
- 默认状况下,使用 Object.defineProperty() 添加的属性值是不可修改的。
咱们要操做属性描述符就得使用这个方法。
defineProperty 这个方法使用来设置属性描述的,对应的 getOwnPropertyDescriptor 就是用来获取属性描述符的。
- 定义:
- Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不须要从原型链上进行查找的属性)
- 语法:
Object.getOwnPropertyDescriptor(obj, prop)
- 参数:
- obj:须要查找的目标对象
- prop:目标对象内属性名称
- 返回值:
- 若是指定的属性存在于对象上,则返回其属性描述符对象(property descriptor),不然返回 undefined。
描述:
- 该方法容许对一个属性的描述进行检索。
- 若是对象中不存在指定的属性,Object.defineProperty()就建立这个属性。
- 当描述符中省略某些字段时,这些字段将使用它们的默认值。
- 拥有布尔值的字段的默认值都是 false。value,get 和 set 字段的默认值为 undefined。
- 一个没有 get/set/value/writable 定义的属性被称为“通用的”,并被“键入”为一个数据描述符。
// 使用表达式的方式建立一个新对象
var person = {
name: "Jack"
};
// 使用 getOwnPropertyDescriptor 获取 name 的属性描述符
console.log(Object.getOwnPropertyDescriptor(person, "name")); // =>
// {value: "Jack", writable: true, enumerable: true, configurable: true}
复制代码
- 能够看到 经过赋值操做添加的普通属性,其属性描述符包含 configurable、enumerable、value、writable
- 默认为 数据描述符
- 且这些值除 value 外,皆为 true,说明该属性能够被 删除、修改、枚举
// 使用 defineProperty 在对象中添加一个属性 - 属性描述符取默认
Object.defineProperty(person, "sex", {});
console.log(person); // => {name: "Jack", sex: undefined}
console.log(Object.getOwnPropertyDescriptor(person, "sex")); // =>
// {value: undefined, writable: false, enumerable: false, configurable: false}
复制代码
- 使用 defineProperty 为 person 添加了一个 'sex' 属性
- 第三个参数,传入了一个空对象
- 可见该属性的 属性描述符 都取了默认值,且该属性默认为 数据描述符
person.sex = "man";
delete person.sex;
console.log(person); // {name: "Jack", sex: undefined}
复制代码
- 因为在 sex 在建立时,属性描述符都取的默认值,writable 为 false,故不能写入
- 一样它也不可被枚举、删除
// 使用 defineProperty 在对象中添加一个属性 - 配置属性描述符
Object.defineProperty(person, "age", {
value: 18,
writable: true,
enumerable: true,
configurable: true
});
console.log(person); // {name: "Jack", age: 18, sex: undefined}
person.age = 16;
console.log(person); // {name: "Jack", age: 16, sex: undefined}
delete person.age;
console.log(person); // {name: "Jack", sex: undefined}
复制代码
- 使用 defineProperty 为对象添加了一个 'age' 属性。且配置了属性描述符
- 能够看到对象多了一个 键为 "age" -- 值为 18 的属性
- 同时,这个值能够被修改、删除、枚举
// 在对象中添加一个 属性与存取描述符
var hobbyValue = [];
Object.defineProperty(person, "hobby", {
get: function() {
return hobbyValue;
},
set: function(newValue) {
hobbyValue.push(newValue);
},
enumerable: true,
configurable: true
});
console.log(person.hobby); // => []
person.hobby = "吃饭";
person.hobby = "睡觉";
person.hobby = "码";
console.log(person.hobby); // => ["吃饭", "睡觉", "码"]
复制代码
- 使用 defineProperty 为 person 对象添加了一个 名为 hobby 的属性
- 属性的 属性描述符 包含 set、get,为存取描述符
- get 在获取属性时调用该函数,返回值将做为属性获取的结果
- set 在设置属性时调用该函数,接收一个参数为要设置的 值
- configurable、enumerable 是通用的
// 数据描述符和存取描述符不能混合使用
Object.defineProperty(person, "conflict", {
value: 0x9f91102,
get: function() {
return 0xdeadbeef;
}
});
// throws a TypeError: value appears only in data descriptors, get appears only in accessor descriptors
复制代码
- 若是属性已经存在,Object.defineProperty()将尝试根据描述符中的值以及对象当前的配置来修改这个属性。
- 若是旧描述符将其 configurable 属性设置为 false,则该属性被认为是“不可配置的”,而且没有属性能够被改变(除了单向改变 writable 为 false)。
- 当属性不可配置时,不能在 数据 和 访问器 属性类型之间切换。
var o = {
a: "aValue",
b: "bValue"
};
console.log(Object.getOwnPropertyDescriptor(o, "a")); // =>
// {value: "aValue", writable: true, enumerable: true, configurable: true}
Object.defineProperty(o, "a", {
get() {
console.log(this === o);
return this.b;
},
set(newValue) {}
});
console.log(Object.getOwnPropertyDescriptor(o, "a")); // =>
// {get: ƒ, set: ƒ, enumerable: true, configurable: true}
复制代码
- 在定义一个 Object o 后,a 属性默认为 数据描述符
- 使用 defineProperty 将 a 属性的描述符 配置成了 存取描述符,拥有 get、set 两个存取函数
- 在 configurable 为 true 的前提下,数据描述符和存取描述符能够互相切换
Object.defineProperty(o, "a", {
configurable: false
});
console.log(Object.getOwnPropertyDescriptor(o, "a")); // =>
// {get: ƒ, set: ƒ, enumerable: true, configurable: false}
Object.defineProperty(o, "a", {
get() {
return 11111;
},
set(newValue) {}
});
// => Uncaught TypeError: Cannot redefine property: a
Object.defineProperty(o, "a", {
value: "aaaa"
// configurable: true
});
// => Uncaught TypeError: Cannot redefine property: a
复制代码
- 当将 a 属性的 configurable 配置成 false 后, 标识“不可配置的”,该属性将不可再被改变
- 包括改变 get、set 函数、数据访问器和存取访问器的切换
- 以及 configurable 也不可再被改变 -- 试图改变时,将会报错
- configurable 是单向的
- 考虑特性被赋予的默认特性值很是重要
- 一般,使用点运算符和 Object.defineProperty()为对象的属性赋值时,数据描述符中的属性默认值是不一样的
- 以下例所示:
var o = {};
o.a = 1;
// 等同于:
Object.defineProperty(o, "a", {
value: 1,
writable: true,
configurable: true,
enumerable: true
});
// 另外一方面,
Object.defineProperty(o, "a", { value: 1 });
// 等同于 :
Object.defineProperty(o, "a", {
value: 1,
writable: false,
configurable: false,
enumerable: false
});
复制代码
在咱们通过一系列的分析后,对于属性描述符也有了必定的了解,建议多推敲下属性描述符部分。 读到这里,咱们回想下规范给予咱们属性是什么的答案:
- 答案 1:属性是由一个名字与一个 ECMAScript 语言类型值和一个 Boolean 属性集合组成。
- 答案 2:由一个名字与一个或两个访问器函数,和一个 Boolean 属性集合组成。访问器函数用于存取一个与该属性相关联的 ECMAScript 语言类型 值。
如今,是否是能够理解规范的答案了,很明显:
- Boolean 属性集合 指的就是 属性描述符
- 一个或两个访问器函数 指的就是 存取描述符 中的 get、set
对应的 规范的第一句: 每一个属性既能够是一个命名数据属性,也能够是一个命名访问器属性
- 命名数据属性:
- 一个命名的属性,有名字,有值,其属性描述符为 数据描述符,其描述符包含value、writable、enumerable、configurable
- 命名访问器属性
- 一个命名的属性,有名字,属性的存取操做由属性描述符中的 getter 与 setter 决定,其属性描述符为存取描述符,描述符包含get、set、enumerable、configurable
从 Object 宏观出发,Object 就是这些 命名数据属性、命名访问器属性、(还有内部属性 - 未在表面变现出来) 的集合体