1、引言
最近,研究了一下JavaScript 关于对象的部分。有些东西刷新了我对 Js 的认知,就比如下面的几个问题:
1、如何删除一个对象的属性?所有对象的属性都是可以删除的吗?
2、for in 在遍历对象的过程中,会存在什么问题?
3、var person1 = new Person() 这个过程发生了什么?
4、什么是构造函数?它在使用的时候有什么样的坑?
5、构造和继承的本质区别是什么?
6、JavaScript常见的设计模式有哪些?是针对什么问题提出的?这些设计模式的缺点又是什么?
2、关于对象的属性类型
(1)、描述
对象拥有属性,一般而言,给一个对象添加一个属性的语法是这样的:
var o = new Object();
o.name = 'mapbar_front';
那么,在添加这样的一条属性,系统为我们做了什么?
对象的每一条属性,都不仅仅代表着一个 value ,还代表着能够对这个 value 进行各种操作的 type!
在给一个对象添加一条属性的时候,系统默认这个对象的属性是可删除的、可for in 遍历的、可读写的。分别对应着这么几条属性 Configurable、Enumberable、Writable。
configurable——默认true,表示能够被delete语句删除这个属性。
enumberable——默认true,表示能够被for in遍历这个属性。
writable——默认true,表示能够改变属性的value值。
value——默认为Undefined。存储这个属性的具体value。
(2)、Object.defineProperty()
JavaScript提供了原生的方法,让我们可以定义一个可配置的属性。
平时我们定义一个属性的方式如下:
var person = {};
person.name = 'mapbar_front';
他是和下面这个是等价的:
var person = {};
Object.defineProperty(person,'name',{
value: 'mapbar_front',
configurable: true,//可delete
enumberable: true,//可for in
writable: true//可读写
});
通过修改configurable、enumberable、writable的值为true/false,我们就能对这个属性的操作权限进行控制。
(3)、其他相关函数
Object.defineProperties()——定义多个属性的函数
Object.defineProperties(book,{
name: {
enumberable: true,
value: 'JavaScript高级程序设计'
},
year: {
configurable: true,
value: 2012
}
})
3、设计模式
在创建对象的过程中,由于会在创建多个对象的时候,产生大量的冗余代码,所以有了工厂模式!
(1)、工厂模式
function createObj(name,age){
var o = new Object();
o.name = name;
o.age = age;
return o;//这个是重点
}
var p1 = createObj('cat',3);
var p2 = createObj('dog',5);
所以,工厂模式的本质就是,返回一个创建好的Object实例。
构造函数的由来:
然而,工厂模式如何来解决对象识别的问题?现在我们只能描述一个事物属于Object类,如何描述一个事物是Cat类,或者是Dog类?所以构造函数的模式就应用而生!
(2)、构造函数模式
//描述dog类
function Dog(){
this.type = 'dog';
this.age = 5;
}
var dog1 = new Dog();
console.log(dog1);//{ type:'dog', age: 5 }
上面的方式,也适合描述一个Cat类,这样我们分清了dog属于Dog类,cat属于Cat类。
(3)、构造函数模式和工厂模式的显著区别
- 没有return。
- 使用this进行指向。
- 使用new 进行实例化。
(4)、构造函数和普通函数区别?
如果说,构造函数和普通函数有什么区别?那么请记住这么一句话:构造函数和普通函数在语法方面无任何区别,但是在功能需求上有很大区别。
构造函数的由来,是为了解决对象创建的问题而存在的!
普通函数的由来,是为了复用代码块而存在的!
//普通函数也可以new
function add(){
console.log(1);
}
var obj = new add();//obj是一个空对象而已
//构造函数能new出属性的唯一原因就是使用了this
(5)、构造函数自身的缺陷
那它有什么问题呢?
function Person(){
this.name = 'mapbar_front';
this.getName = function(){
alert(this.name);
}
}
每次实例化一个对象,getName方法同样也会被实例化,但是getName方法本质上是一个代码块相同的函数。这样极大的损耗了内存空间。(函数要比变量耗费大的多的内存)
(6)、原型模式
function Person(){}
Person.prototype.name = 'mapbar_front';
var p1 = new Person();
var p2 = new Person();
//p1.name 就是'mapbar_front'
//p2.name 也是'mapbar_front'
//他们共享一块内存空间
(7)、原型模式的缺陷
正是因为原型的共享问题,虽然解决了上面描述的占用一块内存空间,但是对象并不是所有的属性都应该被共享。
p1和p2都是Person的实例,他们可以有相同的方法。但是他们的name可能需求就是各自不同。原型的占用一块内存空间的方式是无法解决这个问题的。
比如两个人,可以有相同的工作,甚至姓名都可以相同,但是内在的“思维和意识”,绝对不可能完全相同的。
(8)、原型模式和构造函数模式的组合模式
构造函数解决对象创建的私有问题!
原型模式解决对象创建的共享问题!
两者的结合应该很完美:
function Person(name){
this.name = name;
}
Person.prototype.getName = function(){
alert(this.name);
}
var p = new Person('map');
var p2 = new Person('front');
//实现了属性私有,方法公有,挺好!
那么有没有什么更精简的表达方式??
(9)、动态原型模式
function Person(name){
this.name = name;
if(typeof this.getName !== 'function'){
Person.prototype.getName = function(){
alert(this.name);
}
}
}
这样,我们不必提前把原型方法定义好,在实例调用的时候,就可以动态的添加和调用!!!
还有什么模式?
在JavaScript的标准中,还有两种模式,寄生构造函数模式和稳妥构造函数模式。
只不过都有其很大的局限性,不常用,所以不做其他提示工作。
4、关于原型我们该知道什么?
我们需要知道这样的一些基本东西。
函数都拥有原型,函数的原型使用prototype来指向。在JavaScript中,一旦定义了一个函数,系统就自动了生成了一个函数的原型。
函数的原型是一个对象,函数的原型的构造就是指向自身。
Person.prototype.constructor == Person;//true
(1)、定义了一个构造函数,它的原型对象如何生成
- 定义一个函数的同时,系统会new一个Object的实例。
- 然后把函数的prototype指向这个实例。
- 最后把这个实例的构造再指向函数本身。
所以,原型对象类似下面这样的表示:
Person.prototype = {
construcor: Person,
.....//其他new Object()得到的属性。
}
(2)、new之后发生了什么?
一定注意:
一个构造函数,使用了new操作符,经历了下面几个阶段。
- var o = new Object();
- 把this指向o;
- 把构造函数的作用域也给了这个对象
- 返回这个对象
5、继承
继承的方式有两种,一种是原型链继承,另一种是借用构造函数。
借用构造函数的方式的思想就是,在子类型构造函数内部调用超类型构造函数。
(1)、call、apply
function Super(name){
this.name = name;
}
function Sub(name){
Super.call(this,name);
}
var sub1 = new Sub('mapbar_front');
//继承了Super的name属性
(2)、prototype继承
原型继承,就是把子类型构造函数的原型,指向超类型构造函数的一个实例。
Sub.prototype = new Sup();
一般而言,我们还会把原型的constructor再次指向Sub。
Sub.prototype.constructor = Sub;
不进行上面的指向会有问题吗?
不会有使用上的大障碍,单是会有语法上的紊乱。
var sub = new Sub();
这时候sub.constructor应该按理等于Sub,
但是不指定上面的指向就会是Sup。
(3)、最后得到一个封装的原型继承
function extend(Sub,Sup){
var o = new Object();
o.constructor = Sub;
Sub.prototype = o;
}
6、其他问题
(1)、读取属性的特性
Object.getOwnPropertyDescriptor();
var book = {};
Object.defineProperty(book,'name',{
value:'JavaScript高级程序设计'
});
var descripter = Object.getOwnerPertyDescripter(book,'name');
console.log(descripter.configurable);//true
(2)、判定是否是实例的实例属性
hasOwnerProperty();
var object = { name: 'mapbar_front' };
object.hasOwnerProperty('name');//true
7、我的总结
1、一个对象,拥有属性,这个属性就会有相应的value,除了这个value之外,这个属性还有标记它自身功能权限的东西——configurable、 enumberable、 writable。
2、定义一个具有操作权限的函数的属性:Object.defineProperty()
3、工厂模式:是为创建大量对象而提出,但无法判断对象类型,也就是无法描述class的概念。
4、构造函数模式:解决了类型判定的问题,但是无法解决方法工农共享的问题。
5、原型模式:解决了共享的问题,但是它也正因为共享,成就了它的不完美特性。
6、构造函数+原型模式的组合模式:是目前较为成熟的面向对象编程方案。构造函数解决私有属性问题,原型模式解决共有方法问题。
7、继承有两种方式——原型继承和借用构造函数的继承。
8、原型继承的封装:
function extends(Parent,Child){
var o = new Parent();
o.constructor = Child;
Child.prototype = o;
}
9、借用构造函数的方式主要是靠call和apply来实现。