对象拓展
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]
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
}));