Begin
此对象非彼对象....
上一篇 this 那些事儿介绍了 this 的指向, this 的指向是一个对象. 那么在 JavaScript 中对象是一个什么样的存在, 一起来了解一下对象不为人知的一些故事....
开始吧!
介绍一下 object 家的基本情况
object 是 JS 中基本简单类型家族 6 位成员之一, 分别是: string,number,boolean,null,undefined, 当然还有 object. 对象子类型开枝散叶, 又被称为复杂基本类型, 这里介绍一些特殊的对象子类型:
function, 在 js 中被称为头等公民, 因为它们基本上就是对象, 对象子类型中的内建对象也是 function, 地位很特殊
九种内建对象: String,Number,Boolean,Object,Function,Array,RegExp,Date,Error. 在字面形式调用属性和方法时, JS 引擎自动转为内建对象, 字面形式创建是推荐的, 因为比较简单方便, 比如:
- var strPrimitive = "I am a string";
- strPrimitive.length; //13
- strPrimitive.charAt(3) //"m"
复制代码
深入了解 object
object 的属性访问
有两种方法,. 操作符和 [] 操作符.
. 操作符: 后面只能接标识符(字母, 数字, 下划线, 且数字不能在首位)
[]操作符:[]内放的是字符串的值, 可以是 obj["b"], 也可以是 var a="b";obj[a]通过一个变量返回, 间接的方式
[]操作符另一个使用是计算型属性名, 在键声明位置指定一个表达式:
- var prefix = "foo";
- var myObject = {
- [prefix + "bar"]: "hello",
- [prefix + "baz"]: "world"
- };
- myObject["foobar"]; // hello
- myObject["foobaz"]; // world
复制代码
复制对象
复制对象分浅拷贝和深拷贝.
浅拷贝: Object.assign(),{...obj}
深拷贝:
var newObj = JSON.parse( JSON.stringify( someObj ) );
深拷贝的一个应用是数组去重, 尤其是可以处理数组元素存在数组或者对象情况:
- // 数组去重
- function arrElementOnly(arr) {
- arr = arr.map(ele => JSON.stringify(ele));
- let returnArr = [];
- arr.forEach(ele => {
- if (returnArr.indexOf(ele) === -1) returnArr.push(ele);
- })
- return returnArr.map(ele => JSON.parse(ele));
- }
- var a = [1, 1, 2];
- arrElementOnly(a); //[1,2]
- var b = [1, [1, 2],
- [1, 2]
- ];
- arrElementOnly(b); //[1,[1,2]]
- var c = [1, {
- a: "1"
- }, {
- a: "1"
- }];
- arrElementOnly(c); //[1,{a:"1"}]
- var d = [1, 1, 2, [1, 2],
- [1, 2], {
- a: "1"
- }, {
- a: "1"
- }
- ];
- arrElementOnly(d); //[1,2,[1,2],{a:"1"}]
复制代码
属性描述符
属性描述符分为数据描述符和访问器描述符. 数据描述符使用
Object.getOwnPropertyDescriptor(...)
查看, 使用
Object.defineProperty(...)
定义, 看个例子:
- var myObject = {
- a: 2
- };
- Object.getOwnPropertyDescriptor( myObject, "a" ); // 查看数据描述符
- // {
- // value: 2,
- // writable: true,
- // enumerable: true,
- // configurable: true
- // }
- Object.defineProperty(myObject,"b",{ // 定义数据描述符
- value:3,
- writable:true,
- configurable:true,
- enumerable:true
- })
- myObject.b; //3
复制代码
writable 控制着改变属性值的能力, 非严格模式下修改悄无声息失败, 严格模式下抛出 TypeError
configurable 为 true 时, 属性可配置, 使用
defineProperty(...)
修改数据描述符
- configurable:false,
- defineProperty(...)
修改数据描述符, 无论 strict mode 与否, 都会抛出 TypeError, 这是一个单向操作不可撤销(例外是在这个情况下, writable 可由 true 变为 false)
configurable:false 阻止的另外一个事情是使用 delete 操作符移除既存属性的能力 ,delete 调用无声失败
enumerable 可枚举性, 控制着属性是否能在特定的对象 - 属性枚举操作中出现
谈访问器描述符之前先要谈 *[[Get]]和[[Put]]* 操作:
[[Get]]操作定义了属性访问的行为, 如果当前对象不存在该属性, 就会遍历 [[Prototype]] 链, 如果遍历原型链不存在, 返回 undefined
[[Put]]操作定义设置属性值的行为, 属性存在访问器描述符 setter 吗? 调用 setter:(writable=false? 修改失败或者抛出 TypeError: 设置属性值)
访问器描述符针对某个属性覆盖 [[Get]],[[Put]] 默认操作的一部分, 看 getter 和 setter:
- // 字面量语法定义
- var myObject = {
- // 为 `a` 定义 getter
- get a() {
- return this._a_;
- },
- // 为 `a` 定义 setter
- set a(val) {
- this._a_ = val * 2;
- }
- };
- myObject.a = 2;
- myObject.a; // 4
- // 使用 defineProperty 定义
- Object.defineProperty(myObject,"b",{
- get:function(){return this.a},
- enumerable:true
- })
复制代码
注意: 访问器描述符不能和 writable,value 一起设置, 会报错
- Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
- .
应用
将属性或对象设置为不可改变(p.s. 创建的是浅不可变性, 如果对象拥有对其他对象的引用, 如数组, 对象, 函数等 , 那个对象的内容不会受影响, 依然保持可变), 技术上操作数据操作符:
设置一个对象属性为常量(不能被改变, 重定义, 删除):
defineProperty(...)
设置属性 writable:false 和 configurable:false
防止一个对象添加新的属性:
Object.preventExtensions(...)
对象不可添加新的属性, 对象所有属性不能重定义, 不能删除(属性值可以更改):Object.seal(...), 相当于
Object.preventExtensions(...)
+ 所有对象属性 configurable:false
对象不可添加新属性, 对象所有属性设置为常量: Object.freeze(...), 相当于
Object.preventExtensions(...)
+ 所有对象属性 configurable:false,writable:false
在属性访问时得到 defined 有两种情况, 一种属性不存在返回 undefiend, 另一种属性明确存储值 undefined, 那么该如何区分? 引入属性存在性判断的问题. 看个例子:
- var myObject = {
- a: 2
- };
- ("a" in myObject); // true
- ("b" in myObject); // false
- myObject.hasOwnProperty( "a" ); // true
- myObject.hasOwnProperty( "b" ); // false
复制代码
in 操作符会检查 [[Prototype]] 链, 而
hasOwnProperty(...)
只会检查当前对象. 你能猜到 4 in [2,4,6]的结果是什么吗?
属性枚举性判断. 看个例子:
- var a = {
- name: "Harden"
- };
- var b = Object.create(a);
- b.age = 18;
- Object.defineProperty(b, "hobby", {
- value: "basketball",
- enumerable: false
- });
- for (pro in b) {
- console.log(b[pro]); //Harden,18
- }
- console.log(b.propertyIsEnumerable("name")); //fasle 当前对象不存在
- console.log(b.propertyIsEnumerable("age")); //true
- console.log(b.propertyIsEnumerable("hobby")); //false 属性不可枚举
- console.log(Object.keys(b)); //["age"]
- console.log(Object.getOwnPropertyNames(b)); //["age","hobby"]
复制代码
for..in 遍历对象可枚举属性, 会检查 [[Prototype]] 链;
propertyIsEnumerable(...)
判断在当前对象属性是否存在, Object.keys(...)返回当前对象所有可枚举属性的数组,
Object.getOwnPropertyNames(...)
返回当前对象所有属性.
for..of 循环迭代值, 要求被迭代的东西提供一个迭代器对象. 先看一下数组:
数组使用 for..of 迭代是 ok 的, 因为数组拥有内建的 @@iterator
- var a = [1, 2, 3, 4];
- for (value of a) {
- console.log(value); 1,2,3,4
- }
- // 手动使用 @@iterator
- var b = [1,2,3];
- var it = b[Symbol.iterator]();
- console.log(it.next()); //{ value:1, done:false }
- console.log(it.next()); //{ value:2, done:false }
- console.log(it.next()); //{ value:3, done:false }
- console.log(it.next()); //{ done:true }
复制代码
使用 Symbol.iterator 取得数组的一个内建对象 @@iterator,@@iterator 是一个函数, 函数运行返回一个迭代器对象, 这样就可以 it.next()迭代. 再看对象:
- var myObject = {
- a: 2,
- b: 3
- };
- for (value of myObject) {
- console.log(value);
- } //oops! Uncaught TypeError: myObj is not iterable
复制代码
对象没有内建的 @@iterator, 可以自定义迭代器:
- Object.defineProperty(myObject, Symbol.iterator, {
- enumerable: false,
- writable: false,
- configurable: true,
- value: function() {
- var o = this;
- var idx = 0;
- var ks = Object.keys(o);
- return {
- next: function() {
- let value = o[ks[idx++]],
- done = idx> ks.length;
- return { value, done };
- }
- };
- }
- });
- // 手动迭代
- var it = myObject[Symbol.iterator]();
- console.log(it.next());
- console.log(it.next());
- console.log(it.next());
- for (value of myObject) {
- console.log(value);
- }
复制代码
End
文章为个人总结, 不妥之处还请雅正.
来源: https://juejin.im/post/5b6a8b13f265da0fa21aab66