对象拓展
ES6除了对对象的属性和方法进行简化之外,值得注意的是对象的属性名都会被处理成字符串
例1:
var arr = [5,6,8,88]; console.log(arr['1']); //6
数组是特殊的对象,当你用arr[1]的时候,其实是会经过隐式转换成arr[‘1’]的,所以可以打印出值
例2:
let a = 'hello'; let b = 'world'; let obj = { [a + b] : true, ['hello' + 'world']: undefined }
最后的值是undefined,同样的属性,js不会检查属性值,会直接进行覆盖
例3:
var myObj = {}; myObj[true] = 'foo'; myObj[3] = 'bar'; myObj[myObj] = 'baz'; console.log(myObj); //{'true':'foo','3':'bar','[object Object]': "baz"}
直接变成字符串,属性处理成字符串都要经过隐式转换(toString)
true和3经过原型上的toString方法都会变成对应的字符串,myObj经过toString方法会变成[object Object],所以直接通过[object Object]可以访问到baz
console.log(myObj["[object Object]"]); //baz
例4:
const a = {a: 1}; const b = {b: 2}; const obj = { [a]: 'valueA', [b]: 'valueB' } console.log(obj); //{[object Object]: 'valueB'},都是引用值,会有覆盖值的问题
引用变量一定要加括号,不然就变成{a: “valueA”, b: “valueB”},直接转化不引用了。
获取对象方法的名称
const person = { sayName(){ console.log('hello'); } } console.log(person.sayName.name); //sayName
属性描述符 getOwnPropertyDescriptor
ES5之前没有检测属性特性(是否只读,可遍历等)的方法,ES6提供了属性描述符
let obj = {a: 2}; console.log(Object.getOwnPropertyDescriptor(obj, 'a'));
传入的属性要是字符串
configurable: true //可配置的 enumerable: true //可枚举的 value: 2 writable: true //可写
defineProperty修改和添加一个新的属性
Object.defineProperty(obj, prop, descriptor),descriptor 描述项集合、配置集合
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
应当直接在 Object 构造器对象上调用此方法,而不是在任意一个 Object 类型的实例上调用。因为这个方法是在Object上的,而不是Object.prototype上的,实例不能继承。
let obj = {}; Object.defineProperty(obj, 'a', { value: 2, enumerable: true, configurable: true, //false的话就不能再用defineProperty配置了 writable: false //不可写但是可以删除 }) obj.a = 3;//静默失败,因为不可写,这条语句执行失败,但也不报错;但是在严格模式下会报错 console.log(obj); //{a: 2} console.log(Object.getOwnPropertyDescriptor(obj, 'a')); delete obj.a; console.log(obj); //{}
writable设置false仅仅是不可写,不可删要是configurable为false的情况下才可以
另一种写法:
function defineProperty () { var _obj = {}; Object.defineProperty(_obj, 'a', { value: 1 }); return _obj; } var obj = defineProperty(); console.log(obj);
还可以设置多个属性:defineProperties
function defineProperty () { var _obj = {}; Object.defineProperties(_obj, { a: { value: 1, writable: true, enumerable: true, //可否枚举 configurable: true //可否配置、操作(删除) }, b: { value: 2 } }); return _obj; } var obj = defineProperty(); obj.a = 5; console.log(obj.a);//值在writable:true之前是1,不可修改,加了writable之后改为5
用Object.defineProperty()定义的属性值默认不可修改,不可删除,不可枚举,除非用writable / enumerable修改
configure -> conf(一般用来设置文件夹)/ config(一般用来设置文件名)
getter / setter 取值和赋值
每一个属性定义的时候,都会产生getter和setter的机制
在赋值和取值的时候可以设置一些额外的操作
get操作
let obj = {a: 1}; obj.a; //属性获取,会有[[Get]]的默认操作:查找当前属性,如果没有,查找原型
put操作
obj.a = 3; //赋值操作[[Put]]
- getter 还是 setter
- writable:false,不让你改
- 赋值
getter和setter覆盖了原本的[[Get]]和[[Set]]
getter:取值函数
var obj = { log: ['example', 'test'], get lastest(){ //get方式的伪属性 if(this.log.length === 0){ return undefined; } return this.log[this.log.length - 1]; } } console.log(obj.lastest); //访问属性,实际上调用的是get方法
通过defineProperty定义getter,value和writable不能用
var obj = { get a(){ return 2; } } Object.defineProperty(obj, 'b', { get: function)() { return this.a * 2; } enumerable: true, value: 6 //报错,和get操作重复 writable: true //不管是true还是false都报错,不能定义 })
setter:设值函数
var language = { set current(name){ //一定要有参数 this.log.push(name) }, log: [] } language.current = 'en';
get和set一般情况下都是成对存在的,get和set的函数名称不能直接获取到,要通过getOwnPropertyDescriptor获取到函数才行
var obj = { get foo(){ return this._a; }, set foo(val){ this._a = val * 2; } } obj.foo = 3; //set操作 console.log(obj.foo); //6 console.log(obj.foo.name); //undefined,不能取到name var descriptor = Object.getOwnPropertyDescriptor(obj, 'foo'); console.log(descriptor.get.name); console.log(descriptor.set.name);
应用场景:
<p>0</p>
function defineProperty () { var _obj = {}; var a = 1; Object.defineProperties(_obj, { a: { get () { }, set (newVal) { a = newVal; var oP = document.getElementsByTagName('p')[0]; oP.innerHTML = a; } }, b: { value: 2 } }); return _obj; } var obj = defineProperty(); obj.a = 5;
这样p标签的值就可以改了
function defineProperty () { var _obj = {}; var a = 1; Object.defineProperties(_obj, { a: { get () { return 'a\'s value is '+ a + '.'; }, set (newVal) { console.log('The value has been designed a new value ' + a) } }, b: { value: 2 } }); return _obj; } var obj = defineProperty(); obj.a = 5; //set,5就是传入的newVal值,但是set程序里并没有改变a的值,所以a不变 console.log(obj.a); //get
当我们设置值和取值的时候,得到的结果是
The value has been designed a new value 1 a's value is 1.
数据劫持,指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。
也就是在取值的时候get程序设置了别的程序,让你取不到真的值,阻拦获取和输入。
在全局设置数据值,用get和set方法扩展逻辑
value默认值 / writable 和 set、get不能共存,同时出现会报错!
操作数组
function DataArr () { var _val = null, _arr = []; Object.defineProperty(this, 'val', { get: function () { return _val; }, set: function(newVal) { _val = newVal; _arr.push({val: _val}); console.log('A new value ' + _val + ' has been pushed to _arr'); } }); this.getArr = function(){ return _arr; } } var dataArr = new DataArr(); dataArr.val = 123; dataArr.val = 234; console.log(dataArr.getArr());
对象密封
对象常量:不可删除,不可修改
1. Object.defineProperty配置属性描述符
configurable: false enumerable: true value: 2 writable: false
正常添加属性obj.b=3,默认情况下除了value以外的三个值都是true
通过Object.defineProperty添加属性,默认情况下这三个值都是false
2. preventExtensions 不可拓展(添加新的属性或方法)
preventExtensions不能改变属性描述符
isExtensible 判断对象是否可以拓展
var obj = {a: 2}; console.log(Object.preventExtensions(obj)); //返回该对象 obj.b = 3; //静默失败,严格模式下会报错 console.log(Object.isExtensible(obj)); //false,不可拓展
仍然可以删除,可修改
3. Object.seal(obj)
seal将configurable变成false
isSealed 是否密封
var obj = {a: 2}; console.log(Object.seal(obj));
但是writable还是true的,也就是说可修改
4. Object.freeze(obj)
freeze会把configurable和writable都会是false,这只是浅拷贝,要想深度冻结,需要循环冻结对象上的方法
isFrozen 是否被冻住
function myFreeze(obj) { Object.freeze(obj); for(var key in obj) { if (typeof(obj[key]) === 'object' && obj[key] !== null) { myFreeze(obj[key]); } } }
一般用freeze
对象原型上的其他方法
Object.is()判断是否全等
‘==’会隐式转换
‘===’严格相对运算符,会调用底层的sameValue算法
ES5要判断两个值是否相等,需要用运算符判断
console.log(NaN === NaN); //false console.log(+0 === -0); //true
ES6可以调用is方法判断,用的是‘===’
和ES5不同的结果只有这两种
console.log(Object.is(NaN, NaN)); //true console.log(+0 === -0); //false
Object.keys() 获取自身可枚举的键名
Object.value() 获取自身可枚举的键值
Object.entriese() 获取自身可枚举的键名和键值
不含原型上的属性
coonst foo = {a: 1}; Object.defineProperties(foo, { d: { value: 4, enumerable: true }, f: { value: 5, enumerable: false } }) console.log(Object.keys(foo)); //["a", "d"],f不可枚举 console.log(Object.value(foo)); //[1,4,5] console.log(Object.entries(foo)); //[["a",1],["d",4]]
传入的参数不是对象的话,会隐式转换,进行包装类
let obj = 1; console.log(Object.keys(obj)); //[]
let obj = 'abc'; console.log(Object.keys(obj)); //["1","2","3"]
Object.assign(tar, …sourses)合并对象
对象拷贝(浅拷贝)
let obj = {a: {b: 1}}; let tar = {}; let copy = Object.assign(tar, obj); //返回值就是第一个参数 console.log(copy === tar); //true console.log(copy === obj); //false obj.a.b = 2; console.log(obj.a.b); //2
同名属性的替换:
const tar = {a: 1, b: 1}; const tar1 = {b: 2, c: 2}; const tar2 = {c: 3}; Object.assign(tar, tar1, tar2); console.log(tar); //{a: 1, b: 2, c: 3}后面的覆盖前面的
数组替换:两个属性下标相同的部分会被替换
Object.assign([1,2,3],[4,5]);//[4,5,3]
传值不是对象的情况:
Object.assign(undefined, {a: 1});//报错 var test = Object.assign(1, {a: 1};//用包装类的方式,转换为对象 console.log(test); //Number{1, a: 1}
第一个参数至少要是一个对象,undefined没有对应的包装类,无法进行合并
Object.assign({a: 1}, undefined);//{a: 1} Object.assign({a: 1}, 1);//{a: 1} Object.assign({a: 1}, true);//{a: 1} Object.assign({a: 1}, '123');//{0: "1", 1: "2", 2: "3", a: 1} Object.assign({}, '123', true, 10);//{0: "1", 1: "2", 2: "3"}
如果第二个参数无法转换为对象,就不进行任何处理,直接返回第一个参数的对象
如果第二个参数是对象,还要注意是否具有可枚举性,不具有枚举性的也会忽略
let obj = {a: 1}; Object.defineProperty(obj, 'b', {})
Object.create(proto, [propertiesObject])
第一个参数指定原型,第二个参数配置属性和对应的描述符
var obj = Object.create({foo: 1}, { bar: { value: 2 }, baz: { value: 3, enumerable: true } }) console.log(obj); //{baz: 3, bar: 2} let copy = Object.assign({}, obj); console.log(copy); //{baz: 3},并且原型上的foo没有拷贝上去
继承属性和不可枚举属性,不能拷贝
Symbol也可以用assign进行拷贝,symbol()可以生成完全不一样的,永远不会重复的,类似于字符串的原始类型
var test = Object.assign({a: b}, {[Symbol('c')] : 'd'}); console.log(test)//{a: "b", Symbol(c): "d"}
在原型上扩充:
function Person(){} var age = 1; Object.assign(Person.prototype, { eat(){}, age, })
覆盖的正确做法:
const DEFAULT = { url: { host: 'www.baidu.com', port: 7070 } } function test(option){ option = Object.assign({}, DEFAULT, option); } test({url: {port: 8080}})
用户如果没有给值,就用DEFAULT,如果用户配置了,就会替代DEFAULT
拷贝取值函数:
const source = { get foo(){ return 1; } } const target = {}; Object.assign(target, source); //{foo: 1}
拷贝的不是函数体本身,直接拷贝具体的值
所以用assign不能达到我们的目的,要用getOwnPropertyDescriptor
Object.defineProperties(tar, Object.getOwnPropertyDescriptor(source)); console.log(Object.getOwnPropertyDescriptor(tar, 'foo'));
这个方法完美解决了这个问题,并且连getter和setter也能拷贝
并且getOwnPropertyDescriptors 很容易浅拷贝
利用getPropertyOf获取原型,getOwnPropertyDescriptors获取属性,然后创建对象即可
const clone = Object.create(Object.getPropertyOf(obj), Object.getOwnPropertyDescriptors(obj));
部署对象的方式
const obj = {a: 1};
const obj = Object.create(port);
没有get和set的情况下是可以用assign的
const obj = Object.assign(Object.create(port), { foo: 123 })
const obj = Object.create(port, Object.getOwnPropertyDescriptors({ foo: 123 }));