一、理解对象
1.创建
①构造函数 new Object
②对象字面量 var o = {};
2.属性类型
①数据属性,对象属性有4个属性特征,默认都为true,可以通过Object.defineProperty()来修改属性特征
a.[[Configurable]] 表示能否通过delete删除重新定义,能否修改属性的特征,能否修改为访问权属性
b.[[Enumerable]] 表示能否通过for-in枚举
c.[[Writable]] 表示能否修改属性的值
d.[[Value]] 表示属性值
eg:
var o = {
name : [1, 2, 3]
}
Object.defineProperty(o, "name", {
configurable : false, // 不能delete,不能修改,不能设置为访问器属性
enumerable : false, // 不能枚举
writable : false, // 不能修改
value : [100, 200] // 把值变成[100, 200]
});
// for(var v in o.name) {
// alert(o.name[v]); // 100, 200, 能枚举
// }
alert(o.propertyIsEnumerable("name")); // false
// o.name = "li";
// alert(o.name); // 100, 200 不能修改
// delete o.name;
// alert(o.name); // 100, 200 不能删除
②访问器属性,4个访问器属性特征,可以通过Object.defineProperty()来修改属性特征
a.[[Configurable]] 表示能否通过delete删除重新定义,能否修改属性的特征,能否修改为访问权属性
b.[[Enumerable]] 表示能否通过for-in枚举
c.[[Get]] 表示在读取属性时调用的函数,默认为undefined
d.[[Set]] 表示在设置属性时调用的函数,默认为undefined
eg:
var o = {
name : [1, 2, 3]
}
Object.defineProperty(o, "name", {
get : function () {
alert("get");
},
set : function() {
alert("set");
}
});
o.name = "li"; // set,设置name值时,自动调用o.set()
o.name; // get,读取name时,自动调用o.get()
③定义多个属性 Object.defineProperties()来同时定义多个属性
eg:
var o = {}
Object.defineProperties(o, {
name : {
configurable : false,
value : "zhang"
},
age : {
get : function () {
alert("get");
},
set : function() {
alert("set");
}
}
});
alert(o.name); // zhang
o.age; // get
o.age = "li"; // set
④读取属性的特征 Object.getOwnPropertyDescriptor(objectName, propertyName)
eg:
var o = {}
Object.defineProperties(o, {
age : {
get : function () {
alert("get");
},
set : function() {
alert("set");
}
}
});
var descriptor = Object.getOwnPropertyDescriptor(o, "age");
for(var v in descriptor) {
alert(v + " = " + descriptor[v]); // 弹出访问器属性的4个属性特征
}
二、创建对象
1.工厂模式
①抽象了创建对象的具体过程
eg:
function createObject (name, age) {
var object = new Object();
object.name = name;
object.age = age;
object.sayName = function () {
return object.name;
}
return object;
}
var p1 = createObject("zhang", 23);
var p2 = createObject("li", 33);
alert(p1.sayName()); // zhang
alert(p2.sayName()); // li
// 无法识别p1和p2
alert(p1); // [object Object]
alert(p2); // [object Object]
②弊端 没有解决对象识别的问题
③解决方法 构造函数模型
2.构造函数模式
①创建模式
eg:
function Person(name, age) {
this.name = name;
this.age = age;
this.getName = function () {
return this.name;
}
}
var p1 = new Person("zhang", 34);
var p2 = new Person("li", 23);
alert(p1.getName()); // zhang
alert(p2.getName()); // li
alert(p1 instanceof Person); // true
alert(p2 instanceof Person); // true, 解决了工厂模式的对象识别问题
②问题 每个方法都要在每个实例上创建一遍,从而形成不同的作用域链,从而导致不相同
eg: alert(p1.getName == p2.getName); // false
我们也可以将方法部分提取到构造函数之外,但这样就没有什么封装性可言了。
eg:
function Person(name, age) {
this.name = name;
this.age = age;
this.getName = getName;
}
function getName () {
return this.name;
}
③解决方法 原型模型
3.原型模式
①创建模式 原型对象(构造函数的prototype属性指向它)的好处:可以让所有对象实例共享它包含属性和方法
eg:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function () {
return this.name;
}
var p1 = new Person("zhang", 34);
var p2 = new Person("li", 23);
alert(p1.getName == p2.getName); // true,解决了方法共享的问题
②理解原型
a.函数Person.prototype指向原型
b.Person.prototype.constructor指回构造函数
c.p1、p2的prototype指向原型,且可调用原型中的方法,用Person.prototype.isPrototypeOf(p1)判断,也可以用Object.getPrototypeOf(p1)来获取原型
d.我们可以用原型访问属性的值,但是不能通过实例重写原型的值,因为对象实例的值会屏蔽原型属性的值。当我们用实例对象重写了原型中的值,只有删除实例对象的值,才能访问原型属性的值。
e.同样我们可以通过[实例.hasOwnProperty(propertyName)]来检测实例是否定义了自己的属性值
eg:
function Person() {}
Person.prototype.name = "zhang";
Person.prototype.getName = function () {
return this.name;
}
var p1 = new Person();
alert(p1.name); // zhang
p1.name = "li";
alert(p1.name); // li,实例中的值覆盖了原型中的值
alert(p1.hasOwnProperty("name")); // 判断实例p1是否定义了自己的属性name的值,true
delete p1.name; // 删除实例对象中的属性值
alert(p1.name); // zhang
③原型与in操作符
a.无论是属性值存在于原型中,还是实例对象中都返回true
eg:
function Person() {}
Person.prototype.name = "zhang";
Person.prototype.getName = function () {
return this.name;
}
// 判断是否为原型中的属性
function hasPrototypeProperty(object, propertyName) {
return propertyName in object && !object.hasOwnProperty(propertyName);
}
var p1 = new Person();
p1.name = "li";
alert(hasPrototypeProperty(p1, "name")); // false
delete p1.name;
alert(hasPrototypeProperty(p1, "name")); // true
b.枚举所有可枚举的属性和方法,用Object.key(原型/实例)
eg:
function Person() {}
Person.prototype.name = "zhang";
Person.prototype.age = 11;
Person.prototype.getName = function () {
return this.name;
}
alert(Object.keys(Person.prototype)); // 枚举原型中的属性和方法
var p1 = new Person();
p1.name = "li";
p1.getName = function () {} // 枚举实例对象中的属性和方法
alert(Object.keys(p1));
c.枚举所有的属性和方法,无论是否隐藏,用hasOwnPropertyNames(原型);
eg: alert(Object.getOwnPropertyNames(Person)); // prototype,length,name
④更简单的原型方法
a.源码
eg: function Person() {}
Person.prototype = {
constructor : Person,
name : "zhang",
getName : function () {}
}
b.问题 这样做可能会导致原型中的constructor属性的[Enumerable]为true,默认为false
c.解决方法 用Object.defineProperty()方法重新定义
eg: Object.defineProperty(Person.prototype, constructor, { enumerable : false});
e.实例化对象一定要后于对象的定义完毕
⑤原型对象的问题 共享性,针对方法很好,针对属性也说的过去,但是针对那些包含了引用类型则不可
eg:
function Person() {}
Person.prototype = {
constructor : Person,
friends : [1, 2] // 引用类型
}
var p1 = new Person();
var p2 = new Person();
p1.friends.push(3);
alert(p1.friends);
alert(p2.friends); // 同时返回1,2,3
⑥解决方法 取长补短,用构造函数模式定义属性,用原型模式定义方法
3.组合构造模式和原型模式
①模式 取长补短,用构造函数模式定义属性,用原型模式定义方法
eg:
function Person(name) {
this.name = name;
this.friends = [1, 2] // 引用类型
}
Person.prototype = {
constructor : Person,
name : "zhang",
}
var p1 = new Person("li");
var p2 = new Person("wang");
p1.friends.push(3);
alert(p1.friends); // 1,2,3
alert(p2.friends); // 1,2
②小问题 感觉构造函数和原型分离,破坏了封装性
③解决方法 使用动态原型模式
4.动态原型模式(基本完美) 将原型中方法封装到构造函数中去
eg:
function Person(name) {
this.name = name;
this.friends = [1, 2]; // 引用类型
if (typeof this.getName != "function") {
Person.prototype.getName = {
return this.name;
}
}
}
5.寄生构造模式
①基本思想:创建一个函数(对象),该函数用来封装代码,然后返回函数(对象)
②模式
eg:
function Person(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.getName = function () {
return o.name;
};
return o;
}
var p1 = new Person("zhang", 34);
alert(p1.getName()); // zhang
alert(p1 instanceof Person);// false
③问题:由于实例对象和构造函数完全分离,因此无法识别对象
④案例:对于Array类型,我们可能在特殊情况在,对它进行添加属性和方法
eg:
function NewArray() {
var array = new Array();
array.push.apply(array, arguments);
array.addFun = function () {
return this.join("|");
}
return array;
}
var a1 = new NewArray("zhang", 22);
alert(a1.addFun()); // zhang|22
6.稳妥构造函数模型 没有公共属性,不使用this和new,只能定义获取值的方法
①用途:安全性
②源码
eg:
function Person(name, age) {
var o = new Object();
o.getName = function () {
return name;
}
return o;
}
var p = Person("zhang", 3);
p.name = 'li'; // 无效
alert(p.getName()); // zhang
③特点 函数名首字母大写、对象里只定义方法且不用this、实例化时不用new
④问题:由于实例对象和构造函数完全分离,因此无法识别对象
三、继承
1.原型链
①将父类的实例赋值给子类的原型。因为父类的实例指向父类的原型,因此子类的原型也指向父类的原型。
②基本源码:
eg:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType(); // 将父类的实例赋值给子类的原型
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true,调用父类SuperType的方法getSuperValue()
③别忘记了父类同样基础了祖类Object
③确定原型和实例的关系 用instanceof和对象.isPrototypeOf(实例)
④在子类重新或者添加父类的方法时,必须要在父类定义之后
⑤原型链的问题 原型链中不能存在引用类型
eg:
function SuperType(){
this.friends = [1,2];
}
function SubType(){}
//继承了SuperType
SubType.prototype = new SuperType();
var s1 = new SubType();
var s2 = new SubType();
s1.friends.push(3);
alert(s1.friends); // 1, 2, 3
alert(s2.friends); // 同上
⑥解决方法 借用构造函数
2.借用构造函数 对于原型链中包含引用类型,我们可以在子类的构造函中调用父类的构造函数
①源码案例, 即可以使用引用类型,还可以传递参数
eg:
function SuperType(name){
this.name = name;
this.friends = [1,2];
}
function SubType(){
SuperType.call(this, "abc"); // 传递参数
}
//继承了SuperType
SubType.prototype = new SuperType();
var s1 = new SubType();
var s2 = new SubType();
s1.friends.push(3);
alert(s1.friends); // 1, 2, 3
alert(s2.friends); // 1, 2
alert(s1.name); // abc
③问题 由于是在构造函数中定义,所以方法不能够共享
④解决方法 组合继承
3.组合继承(虽然两次调用了父类,但是基本ok)
①基本思想 将借用构造和原型链结合起来,借用构造定义属性,原型链定义方法
eg:
function SuperType(name){
this.name = name;
this.friends = [1,2];
if (typeof this.getName != "function") {
SuperType.prototype.getName = function () {
return this.name;
}
}
}
function SubType(name, age){
SuperType.call(this, name); // 第二次调用父类
this.age = age;
if (typeof this.getAge != "function") {
SuperType.prototype.getAge = function () {
return this.age;
}
}
}
//继承了SuperType
SubType.prototype = new SuperType(); // 第一次调用父类
var s1 = new SubType("zhang", 23);
var s2 = new SubType("li", 24);
s1.friends.push(3);
alert(s1.friends); // 1, 2, 3
alert(s2.friends); // 1, 2
alert(s1.getName()); // zhang
alert(s2.getAge()); // 24
4.原型式继承
①基本思想 借助原型可以基于已有的对象创建新对象,从而不必自定义对象
eg:
function object(o) {
function F() {};
F.prototype = o;
return new F();
}
var person = {
name : "zhang",
friends : [1, 2]
}
var p1 = object(person);
p1.name = "li";
p1.friends.push(3);
alert(p1.name); // li
alert(p1.friends); // 1,2,3
var p2 = object(person);
p1.name = "wang";
p1.friends.push(4);
alert(p2.name); // wang
alert(p2.friends); // 1,2,3,4
②ECMAScript 5发展了道格拉斯·克罗克福德的原型链继承,用Object.create()方法
eg: 其中第二个参数和defineProperty()方法一致
var person = {
name : "zhang",
friends : [1, 2]
}
var p1 = Object.create(person, {
name : {
value : "zhang"
}
});
p1.friends.push(3);
alert(p1.name); // li
alert(p1.friends); // 1,2,3
var p2 = Object.create(person, {
name : {
value : "wang"
}
});
p1.friends.push(4);
alert(p2.name); // wang
alert(p2.friends); // 1,2,3,4
③问题: 原型链共享问题,引用类型
5.寄生式继承
①思想 基于原型式继承,创建一个新函数对象,添加新方法
eg:
function object(o) {
function F() {};
F.prototype = o;
return new F();
}
function createAnother(original) {
// 继承原来的对象原型
var clone = object(original);
// 添加新方法
clone.newFun = function () {
return "new function";
}
return clone;
}
var person = {
name : "zhang",
friends : [1, 2]
}
var p = createAnother(person);
alert(p.name); // zhang
alert(p.newFun()); // new function
②问题 原型链共享问题,引用类型
6.寄生组合式继承
①思想 在组合继承和原型式继承的基础上,不在子类的内部调用父类的构造函数,而是创建父类原型的副本
eg:
function object(o) {
function F() {};
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType) {
// 赋值proto为superType的原型
var proto = object(superType.prototype);
// 原型的contructor属性指向构造函数
proto.contructor = subType;
// superType的构造函数指向原型
subType.prototype = proto;
}
function SuperType(name){
this.name = name;
this.friends = [1,2];
if (typeof this.getName != "function") {
SuperType.prototype.getName = function () {
return this.name;
}
}
}
inheritPrototype(SubType, SuperType);
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
if (typeof this.getAge != "function") {
SuperType.prototype.getAge = function () {
return this.age;
}
}
}
var s1 = new SubType("zhang", 23);
var s2 = new SubType("li", 24);
s1.friends.push(3);
alert(s1.friends); // 1, 2, 3
alert(s2.friends); // 1, 2
alert(s1.getName()); // zhang
alert(s2.getAge()); // 24