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
文章为个人总结,不妥之处还请雅正。
转载请注明出处。
参考文献: