js中创建一个自定义对象有两种方法,一种是使用new,另一种是使用对象字面量形式,至于构造函数模式、工厂模式、原型模式、组合模式、寄生模式、es6中的class、Object.create()等等,都不过是这两种方法的组合应用
在创建一个对象字面量前,要记住几点:
这种对象不是一个构造函数,不能使用new进行实例化
它是引用类型,也就意味着对象名是一个指针,当你把对象名赋值给另一个变量时,你在新变量上所做的任何操作都会影响源对象
var obj={
id:123
}
var b=obj
b.id=456;
console.log(obj.id) //456
- 对象中的所有成员默认是公开的,如果想实现私有成员,只能采用es6的Symbo来定义一个成员名,然后采用export模块化来达到隔离效果。
一、对象字面量语法
var person={
name:'小王',
age:18,
_pri:233
}
-成员名称的单引号不是必须的
最后一个成员结尾不要用逗号,不然在某些浏览器中会抛出错误
成员名相同会发生什么?
es5普通模式下后定义的会覆盖前面定义的,严格模式则会报错,es6则不管什么模式都采用后面的覆盖前面的
成员名可以是动态变量吗?
es5只能在对象字面量表达式申明以后再添加
var dynamicVar="dyna";
var person={
}
person[dynamicVar]='123';
console.log(person[dynamicVar])
es6则更符合使用场景,可在表达式内创建动态的成员名
var dynamicVar="dyna";
var person={
[dynamicVar]:'test'
}
console.log(person[dynamicVar])
es6中如果想使用表达式外面的变量名作为成员名,变量的值作为成员值,可进一步简写为
var dynamicVar="dyna";
var person={
dynamicVar, //这是一个语法糖,js引擎会解释为dynamicVar:'dyna'
age:15
}
console.log(person.dynamicVar)
注意:此时不能采用person[dynamicVar]
方式访问,因为这句话js引擎会解释为person['dyna']
,对象中没有dyna
属性,肯定就是undefined
了
可以采用new person()
的方式使用吗?
肯定是不可以,new关键字后面必须是一个构造函数才行,对象字面量哪来的构造函数
二、对象成员配置
对象申明后,会默认为内部的每个成员(属性或方法)生成一些隐藏属性,这些隐藏属性是可以读取和可配置的:
Object.getOwnPropertyDescriptor()或getOwnPropertyDescriptor()-读取成员的隐藏属性
Object.definePropertype或Object.defineProperties
----设置成员的隐藏属性
相应的隐藏信息如下:
1.configurable
是否可以删除某个成员,默认为true,需要注意的是,如果该属性如果定义为false,后续又定义为true的话会报错
Object.defineProperty(person,'name',{
configurable:false
})
Object.defineProperty(person,'name',{
configurable:true
})
2. writable
成员是否可写,默认为true
Object.defineProperty(person,'name',{
writable:false
})
person.name='小李'; //属性不可写,严格模式下会报错
console.log(person.name); //输出小王,说明上面一句无效
3. enumerable
成员是否可被枚举,默认为rue
,该属性主要是用来防范Object.keys()
和for in
的,也就是说该属性设置对于Object.getOwnPropertyNames()
方法是无效的。
使用相应的枚举方法,输出的结果是一个数组,那么数组中元素的顺序是按什么规则组织的呢?
在es5中并没有明确这一点,各个浏览器有自己的实现,es6中采用Object.keys()
和for in
方法时还是没有明确,但采用Object.getOwnPropertyNames()
方法枚举时,有了相应的标准:
最先放入数组中的是数值型的成员名,按升序排列;
其次是其它类型的,按添加的先后顺序排列
var obj={
3:'我是1',
1:'我是1',
b:'我是b',
a:'我是a'
}
var names=Object.getOwnPropertyNames(obj);
console.log(names) //["1","3","b","a"]
4. get与set
读写成员时调用的函数,默认为undefined
在文章最开始处的对象定义中,我们创建了一个_pri成员,表示这个成员应在内部读取,下划线只是一个标记符,并不能限制该成员只能在对象内部访问。接下来我们来封装一个属性读写器对_pri成员进行读取,读写器名称随意取,这里叫pri只是为了可读性
Object.defineProperty(person,'pri',{
get:function(){
//做一些其它操作
console.log('准备获取_pri的值')
return _pri;
},
set:function(newValue){
_pri =newValue
}
})
person.pri='456';
console.log(person.pri);
如果只有get则表示属性值是只读的,只有set表示只能写。
属性读写器最常用的场景就是在读取或写入属性前可以附带的做一些操作,达到更好的封装性
三、对象保护
禁止添加成员
Object.preventExtensions()
该方法用于阻止向对象添加成员,使用Object.isExtensible()`判断对象是否可添加成员
Object.preventExtensions(person);
//添加成员无效,非严格模式下什么都不会发生,严格模式下会报错
person.bankAccount='中国农业银行'
//依然可以删除成员,证明了preventExtensions方法只能阻止添加方法
delete person.age;
console.log(person.age) //undefined,表明删除成功了
禁止添加、删除成员
Object.seal()
用来阻止添加或删除成员,判断对象是否是密封的可采用Object.isSealed()
禁止任何操作
使用Object.freeze()
方法后,除了不能添加删除成员,连成员的赋值都会失效,但是写入属性(上面set定义的)依然是有效的
四、其它技巧
实现继承
Object.create(person)
可产生一个具有继承特性的新对象,但是需要注意的是,父对象的引用类型成员是和子对象共享的,当子对象修改引用类型的成员时,父对象的该成员也会同步发生变化
var person={
name:'小王',
age:18,
_pri:233,
gf:['豆得儿','张G','TFb']
}
var child=Object.create(person);
child.gf.splice(0,1); //跟豆得儿分手了
console.log(person.gf.length) //父类的gf也变成2了,父子共享女友,尼玛,太乱了
es6中的Object.setPrototypeOf(obj, prototype)方法可将已有的对象变成继承关系,其内部原理也跟Object.create一样,都是将子对象的prototype指向父对象,该方法实现的继承依然有父子对象共享了引用类型成员的问题。
var person={
age:15
}
var man={
}
Object.setPrototypeOf(man,person)
console.log(Object.getPrototypeOf(man)===person) //true
console.log(man.age); //15
重写父对象的成员
直接在子对象中定义一个同名的成员即可
子对象中访问父对象的成员
super
关键字是es6新增的,它是一个指针,指向当前对象的原型,也就是父对象
var person={
age:15,
testMethod(){
console.log('我是父类方法')
}
}
var man={
//重写父类方法
testMethod(){
console.log('我是子类方法')
super.testMethod();
}
}
Object.setPrototypeOf(man,person)
man.testMethod();
需要注意的是,如果两个对象不是继承关系,使用super关键字会报错
实现jquery.extend
jquery.extend
是一个典行的对象混入,所谓对象混入,就是将n个对象(为了便于表述,直接叫做输入对象)组合成一个新对象,新对象具有各个输入对象的特征,这在软件设计模式中叫做装饰器模式,在es6以前需要自己实现,核心代码如下:
function mixins(target,sourceArr){
sourceArr.forEach(function(source){
Object.keys(source).forEach(function(item){
target[item] = source[item]
})
})
return target
}
var obj1={
name:'123'
}
var obj2={
id:100
}
var obj3={
meth(){
console.log('haha')
}
}
var target=mixins(obj1,[obj2,obj3])
target.meth()
上面的代码实现了一个简易版的jquery.extend
的浅拷贝模式(也就是deep参数为false时的功能),如果多个对象成员同名,则后面的会覆盖前面的,该代码如果要在正式环境使用,还需要加不少的判断代码,但是在es6中一句话就可以实现mixins()
函数的功能。
var target=Object.assign(obj1,obj2,obj3)
需要注意的一点就是输入对象中使用了get修饰符,这时后会有一个转换,方法名变成了新对象的属性名,其值为get修饰符方法中的返回值
var obj1={
name:'123'
}
var obj2={
id:100
}
var obj3={
get getMethod(){
return '123'
},
meth(){
console.log('haha')
}
}
var target=Object.assign(obj1,obj2,obj3)
console.log(target.getMethod) //target.getMethod()会报错,原因是发生了转换