js 面向对象
对象理解
属性类型
属性定义方法:ie8不支持
-
Object.defineProperty(obj, propertyName, 包含修改内容的对象)
- 数据属性的四个特征:
[[Configurable]]
可否通过delete删除属性并重新定义
[[Enumerable]]
可否枚举/通过for-in返回属性
[[Writable]]
可写
[[Value]]
值 - 修改访问器属性的两个函数
getter
和setter
函数
- 数据属性的四个特征:
-
Object.definePropertyies(obj, {修改的属性})
特性读取
Object.getOwnPropertyDescriptor()
---返回一个对象
创建对象模式
工厂模式
定义函数,接收参数作为属性值,新建一个对象,并返回赋值后的对象。
function createCat(name, age, color){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.color = color;
return obj;
}
var cat1 = createCat("mimi", 2, "yellow");
构造函数模式
function Cat(name, age, color){
this.name = name;
this.age = age;
this.color = color;
this.getName = function(){ return this.name; };
}
var cat2 = new Car("lucky", 1, "white");
var cat3 = new Car("cathy", 1, "black");
!!!new
操作符的步骤:
- 创建一个空对象
- 将
this
指向新对象(构造函数的作用域赋给这个对象) - 执行构造函数给对象添加属性
- 返回新对象
问题:
每个实例都会新建 Function
实例,实例的机制相同,但是作用域和标识符解析不同,是不必要的开销。
解决:将方法定义在外部。
function Cat(name, age, color){
this.name = name;
this.age = age;
this.color = color;
this.getName = getName;
}
function getName()
{
return this.name;
}
原型模式
function Person(){};
Person.prototype.name = "jack";
通过 Person.prototype
可以给原型添加属性和方法,也可以通过定义对象字面量一次添加多个,但是会破坏 constructor
指向,可以再修改回来。而且通过定义对象字面量重写原型对象会导致原型链断开:
function Person(){};
var person = new Person();//先实例化
Person.prototype = {//再重写原型对象
constructor: Person,
name:john,
age: 20,
getName: function(){}
};
//person.getName()会报错,因为person指向的是旧的原型对象,其中没有这个方法
组合模式
在构造函数中定义非引用数值属性,在原型中定义共享属性 constructor
和方法。
function Person(name, age, eyeColor){
this.name = name;
this.age = age;
this.eyeColor = eyeColor;
};
Person.prototype = {
constructor: Person,
getName: function(){}
};
动态原型模式
function Person(name, age, eyeColor){
this.name = name;
this.age = age;
this.eyeColor = eyeColor;
if(typeof this.getName != "function"){//只有getName不存在的时候才会调用,就是初次调用构造函数的时候
Person.prototype.getName = function(){};
}
};
寄生构造函数模式
function createCat(name, age, color){
var obj = new Object();
obj.name = name;
obj.age = age;
obj.color = color;
return obj;
}
var cat1 = new createCat("mimi", 2, "yellow");
对象和构造函数无关系
稳妥构造函数模式
原型与原型链
原型对象
- 创建一个函数时会自动创建一个原型对象,只有函数拥有
prototype
属性,【prototype】指向函数的【原型对象】 prototype
原型对象中有【constructor属性】指回向【构造函数】。- 注: ES6 的箭头函数没有
prototype
属性,但是有__proto__
属性。
原型
-
prototype
是【基于构造函数构造的实例对象】的隐式原型([[prototype]]
,部分浏览器可以通过__proto__
属性访问),即[[prototype]]
指向【其构造函数的原型对象】。- 从构造函数A实例化对象a,那么【a的原型】就是【A的原型对象
prototype
】。
- 从构造函数A实例化对象a,那么【a的原型】就是【A的原型对象
-
所有的引用类型都有
__proto__
属性
原型链与继承
原型链是继承的一种实现方法:利用原型使【引用类型A】继承【引用类型B】的属性和方法
引用类型值属性会被所有实例共享
- 有构造函数A、B,B继承自A,C为B的一个实例
【实例 C 的__proto__
】指向【B prototype
原型对象】,【B prototype
的constructor
属性】指向【构造函数B】,【B prototype
的__proto__
】指向【A prototype
原型对象】。
实际上是 __proto__
连接【实例】和【原型对象】形成原型链。
C.__proto__ === B.prototype //true
C.__proto__.__proto__ === A.prototype //true
//没有修改 constructor
function Animal(){
this.name = 'animal';
}
function Cat(){
this.name = 'Cat';
}
function ChinaCat(){
this.name='chinacat';
}
Cat.prototype = new Animal(); //继承Animal
ChinaCat.prototype = new Cat(); //继承Cat
//插入 constructor 修改语句
var a = new ChinaCat();
结果如图:
此时
- 【实例a】, 【ChinaCat原型对象】,【Cat原型对象】的
constructor
都会指向【function Animal(){}
】
因为a.__proto__
指向 ChinaCat 原型对象,ChinaCat 原型对象的__proto__
指向 Cat 原型对象,Cat 原型对象的__proto__
指向 Animal 原型对象,Animal 原型对象中的constructor
指向【function Animal(){}
】.
注:实例 a 的prototype
为空,因为prototype
是函数有的属性
//插入constructor修改语句
Cat.prototype.constructor = Cat;
ChinaCat.prototype.constructor = ChinaCat;
var a = new ChinaCat();
此时
- 【
a.constructor
】指向其【构造函数function ChinaCat(){}
】,【a.__proto__
】等于其原型对象中的__proto__
所指向的【Cat原型对象】, - 【
ChinaCat
原型对象】
ES5继承
绑定构造函数
缺点:父类型中定义的方法不可继承,对子类型不可见,只能通过构造函数,无法进行函数复用。
function Parent(){
this.color = ['red','blue','yellow'];
}
function Child(){
parent.call(this); //parent.apply(this,arguments);
}
var ins = new Child();
ins.color.push('white');//ins.color = ['red','blue','yellow','white'];
原型链继承
缺点:无法向父类构造函数中传递参数;子类原型链上定义的方法有先后顺序问题;引用类型值的原型属性会被共享。
需要注意的一点是要修改constructor的指向。
function Animal(species) {
this.species = species;
}
Animal.prototype.func = function() {
console.log("Animal");
};
function Cat() {}
/*func方法是无效的, 因为后面原型链被重新指向了Animal实例*/
Cat.prototype.func = function() {
console.log("Cat");
};
Cat.prototype = new Animal();//原型继承
//Cat.prototype.constructor属性会指向Animal,而不是Cat,需要手动修改
Cat.prototype.constructor = Cat; // 修复: 将Cat.prototype.constructor重新指向Cat
var cat = new Cat();
cat.func(); // output: Animal
console.log(cat.species); // undefined,不能传递参数
组合继承
- 用原型链实现对原型属性和方法的继承
- 借用构造函数实现对实例属性的继承。
缺点:调用了两次父类构造函数
function Animal(type){//构造函数,定义属性
this.type = type;
this.deployment = [];
}
Animal.prototype.getType= function(){//原型对象定义方法
return this.type;
};
//继承属性
function Cat(name, age){//构造函数,继承并定义自己的属性
Animal.call(this, "cat");
this.name = name;
this.age = age;
}
//继承方法
Cat.prototype = new Animal(); //第一次调用 获得Animal的实例属性typedeployment
Cat.prototype.constructor = Cat; //修改constructor指向
//实例化对象
var cat1 = new Cat("lucky",2);//第二次在Cat构造函数中调用 会重新获得实例属性,从而屏蔽原型中的同名属性
cat1.deployment.push("china");
var cat2 = new Cat("lucy",1);//第二次在Cat构造函数中调用 会重新获得实例属性,从而屏蔽原型中的同名属性
cat2.deployment.push("canada");
//
console.log(cat1);
console.log(cat2);
实际上,就有了两组相同的属性:一组在cat1/cat2实例中,一组在Cat原型对象中。
寄生组合式继承
解决组合继承的问题,是理想的继承方法
- 用构造函数继承属性
- 通过原型链混成形式继承方法
function inherentPro(subType, superType){
var prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
inherentPro(Cat, Animal);//替换Cat.prototype = new Animal();