一、创建对象的方法(6种)
1.工厂模式
即用函数来封装以特定接口创建对象的细节。
function createPerson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); } return o; } var person1 = createPerson(‘Lily‘,17,‘Teacher‘); var person2 = createPerson(‘Simon‘,22,‘Doctor‘);
弊端:无法解决对象识别的问题(即怎么知道一个对象的类型)。
2.构造函数模式
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); } } var person1 = new Person(‘Lily‘,17,‘Teacher‘); var person2 = new Person(‘Simon‘,22,‘Doctor‘);
构造器函数始终都应该以大写字母开头;要创建新的实例,必须使用new操作符。
与工厂模式的区别:
A、没有显式地创建对象
B、直接将属性和方法赋给了this对象
C、没有return语句
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,这正是构造函数胜于工厂模式的地方。
检测对象类型可使用 instanceof 操作符:
alert(person1 instanceof Object);//true alert(person1 instanceof Person);//true alert(person2 instanceof Object);//true alert(person2 instanceof Person);//true
构造函数的弊端: 每个方法都要在每个实例上重新创建一遍。
在之前的例子中,person1和person2都有一个名为sayName()的方法,但两个方法不是同一个Function的实例。(因为,ECMAScript中的函数是对象,因此每定义一个函数,实际上就是实例化一个对象)从逻辑上讲,此时的构造函数也可以这样定义:
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = new Function(‘alert(this.name)‘);//与声明函数在逻辑上是等价的。 }
以这种方式创建函数,会导致不同的作用域和标识符解析。
3.原型模式
function Person(){}; Person.prototype.name = ‘Lily‘; Person.prototype.age = 17; Person.prototype.job = ‘Teacher‘; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); alert(person1.sayName);//‘Lily‘ var person2 = new Person(); alert(person2.sayName);//‘Lily‘ alert(person1.sayName == person2.sayName);//true
通过 isPrptotypeOf() 方法可以确定实例与某对象原型之间的关系:
alert(Person.prototype.isPrototypeOf(person1));//true alert(Person.prototype.isPrototypeOf(person2));//true
因为person1和person2都有一个指向Person.prototype的的指针,因而都返回了true。
虽然可以通过实例对象访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那么新创建的属性就会屏蔽原型中的那个属性:
function Person(){}; Person.prototype.name = ‘Lily‘; Person.prototype.age = 17; Person.prototype.job = ‘Teacher‘; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); var person2 = new Person(); person1.name = ‘Simon‘; alert(person1.name);//‘Simon‘ 来自实例 alert(person2.name);//‘Lily‘ 来自原型
即,为对象实力添加一个属性时,该属性就会屏蔽原型对象中的同名属性;换句话说,添加这个属性只会阻止我们访问原型中的那个同名属性,而不会修改那个属性。即使将这个属性设置为null,也无法恢复其指向原型的链接。
想完全删除实例属性,可使用 delete 操作符:
function Person(){}; Person.prototype.name = ‘Lily‘; Person.prototype.age = 17; Person.prototype.job = ‘Teacher‘; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); var person2 = new Person(); person1.name = ‘Simon‘; alert(person1.name);//‘Simon‘ 来自实例 alert(person2.name);//‘Lily‘ 来自原型 delete person1.name; alert(person1.name);//‘Lily‘ 来自原型
使用 hasOwnProperty() 方法可以检测一个属性是存在于实例中,还是原型中(存在于实例中则返回true):
function Person(){}; Person.prototype.name = ‘Lily‘; Person.prototype.age = 17; Person.prototype.job = ‘Teacher‘; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); alert(person.hasOwnProperty(‘name‘));//false person1.name = ‘Simon‘; alert(person.hasOwnProperty(‘name‘));//true
使用 in 操作符能够检测某对象是否具有某个属性(无论该属性是存在于实例中还是原型中):
function Person(){}; Person.prototype.name = ‘Lily‘; Person.prototype.age = 17; Person.prototype.job = ‘Teacher‘; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); alert(person.hasOwnProperty(‘name‘));//false alert(‘name‘ in person1); //true
person1.name = ‘Simon‘; alert(person.hasOwnProperty(‘name‘));//true alert(‘name‘ in person1); //true
故而,检测某属性是否存在于原型中可使用函数↓
function hasPrototypeProperty(object,name){ return !object.hasOwnProperty(name) && (name in object); }
只要in操作符返回true而hasOwnProperty()返回false,即可确定属性是原型中的属性。
要取得对象上所有可枚举的实例属性,可使用ES5的 object.keys() 方法。该方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组:
function Person(){}; Person.prototype.name = ‘Lily‘; Person.prototype.age = 17; Person.prototype.job = ‘Teacher‘; Person.prototype.sayName = function(){ alert(this.name); } var keys = Object.keys(Person.prototype); alert(keys); //"name,age,job,sayName"; alert(Array.isArray(keys)); //true alert(keys.length); //4 var person1 = new Person(); person1.name = "Candy"; person1.job = "Singer"; var keys2 = Object.keys(person1); alert(keys2); //"name,job";
constructor 属性不可枚举。若想获得所有实例属性(无论是否可枚举),则可使用 Object.getOwnPropertyName() 方法:
function Person(){}; Person.prototype.name = ‘Lily‘; Person.prototype.age = 17; Person.prototype.job = ‘Teacher‘; Person.prototype.sayName = function(){ alert(this.name); } var keys = Object.getOwnPropertyName(Person.prototype); alert(keys); //"constructor,name,age,job,sayName";
将 Person.prototype 设置为等于一个以对象字面量创建的新对象,是更为简单的原型语法:
function Person(){}; Person.prototype = { name: ‘Lily‘, age: 17, job: ‘Teacher‘, sayName: function(){ alert(this.name); } };
但这样的语法,本质上是完全重写了默认的 prototype 对象,使得 constructor 属性不再指向Person了(指向Object构造函数)。
可通过以下方式将constructor属性重新指向Person:
function Person(){}; Person.prototype = { constructor: Person, //重设constructor属性 name: ‘Lily‘, age: 17, job: ‘Teacher‘, sayName: function(){ alert(this.name); } };
但这种方式会导致它的[[Enumerable]](可枚举)特性被设置为true。详见高程P155.
对原型对象所做的任何修改都能立即从实例上反映出来,即便是先创建了实例后修改原型:
var friend = new Person(); Person.prototype.sayHi = function(){ alert(‘Hi!‘); }; friend.sayHi(); //"Hi!"
但若是重写整个原型对象,情况就不一样了:
var friend = new Person(); Person.prototype = { construcor: Person, name: ‘Candy‘, age: 22, job: ‘Dancer‘, sayName: function(){ alert(this.name); } }; friend.sayName(); //error
因为,调用构造函数时会为实例添加一个指向最初原型的指针,而把原型修改为另外一个对象就等同于切断了实例与最初原型之间的联系。
实例中的指针仅指向原型,而不指向构造函数。
重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系,他们引用的仍然是最初的原型。
原型对象的弊端:当属性中包含引用类型值时,修改实例中相应的引用类型值将会间接修改原型中的引用类型值:
function Person(){}
Person.prototype = { construcor: Person, name: ‘Candy‘, age: 22, job: ‘Dancer‘, friends: [‘Mary‘,‘June‘], sayName: function(){ alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push(‘Van‘); alert(person1.friends); //"Mary,June,Van" alert(person2.friends); //"Mary,June,Van" alert(person1.friends === person2.friends); //true
4.组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性:
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.friends = [‘Lily‘,‘Candy‘]; } Person.prototype = { constructor: Person, sayName: function(){ alert(this.name); } } var person1 = new Person(‘Nicholas‘,29,‘Software Engineer‘); var person2 = new Person(‘Greg‘,27,‘Doctor‘); person1.friends.push(‘Simon‘); alert(person1.friends); //"Lily,Candy,Simon" alert(person2.friends); //"Lily,Candy" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
5.动态原型模式
6.寄生构造函数模式(和工厂模式一样)
7.稳妥构造函数模式
稳妥对象:没有公共属性,其方法也不引用this的对象。
稳妥构造函数遵循与寄生构造函数类似的模式,不同之处在于:新创建的实例方法不引用this;不使用new操作符调用构造函数。
function Person(name,age,job){ //创建要返回的对象 var o = new Object(); //可以在这里定义私有变量和函数 //添加方法 o.sayName = function(){ alert(name); }; //返回对象 return o; } var friend = Person(‘Nicholas‘,22,‘Software Engineer‘); friend.sayName(); //"Nicholas"
二、继承
1.原型链继承
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。故而,使原型对象等于另一个类型的实例,就实现继承:
A.prototype = new B(); //继承了B
原型链继承的实质:重写原型对象。
当以读取模式访问一个实例属性时,首先会在实例中搜索该属性,若没有找到该属性,则会继续搜索实例的原型。在通过原型链继承的情况下,搜索过程就得以沿着原型链继承向上。
所以引用类型都默认继承了Object,这个继承也是通过原型链实现的。所以函数的1默认原型都是Object的实例。P164
确定原型和实例之间的关系:使用 instanceof 和 isPrototypeOf() 方法。
alert(A instanceof B); //boolean // A是B的实例,则返回true,反之返回false
alert(A.prototype.isPrototypeOf(B)); //boolean //A的原型也是B的原型
给原型添加方法的代码一定要放在替换原型的语句之后。
在通过原型链实现继承时,不能使用对象字面量创建原型方法,这样会重写原型方法。P166
原型链继承的弊端:问题源于包含引用类型值的原型,包含引用类型值的原型属性会被所有实例共享。
2.借用构造函数继承
即,在子类型构造函数的内部调用超类型构造函数,通过 apply() 和 call() 实现:
function Sub(){ Sup.call(this); //Sub继承了Sup }
弊端:方法都在构造函数中定义,函数复用无从说起。
3.组合继承
将借用构造函数和原型链组合起来。
思路:通过原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。
4.原型式继承
5.寄生式继承
6.寄生组合式继承
还是直接看书更好理解啊朋友们~