js面对对象(创建对象,实现继承)
Opened this issue · 0 comments
创建对象
创建单个对象可以用对象字面量,object构造函数(new Object() )方法来创建,为啥还要单独拿出来讲呢?因为这些方式有个明显的缺点:使用同 一个接口创建很多对象,会产生大量的重复代码。
为了写质量更高的代码,需要学习这些创建对象模式。
创建对象是下文实现继承的基础。
工厂模式 (创建对象)
也是一种是软件工程领域一种广为人知的设计模式
思路
:在一个函数内创建一个空对象,给空对象添加属性和属性值,return这个对象。然后调用这个函数并传入参数来使用。
实例
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){ alert(this.name); };
return o;
}
var person1 = createPerson("xm", 22);
var person2 = createPerson("Greg", 27);
console.log(person1.name) //xm
console.log(person1.sayName()) //xm
优点
:解决了创建 多个相似对象的问题
缺点
:没有解决对象识别的问题(即怎样知道一个对象的类型)
构造函数模式(创建对象)
思路
:创建一个构造函数,然后用new 创建构造函数的实例。
实例
function Person(name, age, job){ //按照惯例,构造函数应以一个 大写字母开头,而非构造函数应以一个小写字母开头。
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name);
};
}
var person1 = new Person("xm", 22);
var person2 = new Person("Greg", 27);
console.log(person1.name) //xm
console.log(person1.sayName()) //xm
优点
:1.子类型构造函数中可向超类型构造函数传递参数。
2.方法都在构造函数中定义,对于属性值是引用类型的就可通过在每个实例上重新创建一遍,避免所有实例的该属性值指向同一堆内存地址,一个改其他也跟着改。
缺点
:对于一些可共用的属性方法(比如这边的this.sayName)没必要都在每个实例上重新创建一遍,占用内存。(无法复用)
原型模式(创建对象)
思路
:创建一个函数,给函数原型对象赋值。
具体一点就是利用函数的prototype属性指向函数的原型对象,从而在原型对象添加所有实例可共享的属性和方法。
原型图
实例
function Person(){ }
Person.prototype.name = "xm";
Person.prototype.age = 22;
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
console.log(person1.name) //xm
console.log(person1.sayName()) //xm
优点
:可以让所有对象实例共享它所包含的属性和方法(复用性)。
缺点
:1.在创建子类型的实例时,不能向超类型的构造函数中传递参数。
2.如果包含引用类型值的属性,那一个实例改了这个属性(引用类型值),其他实例也跟着改变。
组合使用构造函数模式和原型模式(创建对象,推荐)
思路
:构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。简单来说就是属性值是引用类型的就用构造函数模式,方法和属性能共享的就用原型模式,取精去糟。
实例
function Person(name, age){ //构造函数模式
this.name = name;
this.age = age;
this.friends = ["aa", "bb"];
}
Person.prototype = { //原型模式
constructor : Person,
sayName : function(){
console.log(this.name);
}
}
Person.prototype.hobby = {exercise:"ball"}; //原型模式
var person1 = new Person("xm", 22);
var person2 = new Person("Greg", 27)
person1.friends.push("cc");
console.log(person1.friends); //"aa,bb,cc"
console.log(person2.friends); //"aa,bb"
person1.sayName = function(){console.log(this.age)};
person1.sayName(); //22
person2.sayName(); //Greg
person1.hobby.exercise = "running";
console.log(person1.hobby); //{exercise: "running"}
console.log(person2.hobby); //{exercise: "running"}
注意&疑问
:js引用类型有object,数组,函数,这里原型模式为啥object,数组会如实地一个实例改,其他跟着改;而函数却一个实例改,其他没跟着改。有知道答案的麻烦一定要告诉我下😂
优缺点
:构造函数模式和原型模式的取精去糟。
继承
原型链(继承)
思路
:利用原型让一个引用类型继承另一个引用类型的属性和方法。
原型链图
我画成这样更好理解
核心code
:
SubType.prototype = new SuperType();
实例
function SuperType(){
this.colors = ["red", "blue", "green"];
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){};
//继承了 SuperType
SubType.prototype = new SuperType();
//添加新方法
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
//覆盖超类型中的方法(父类的原型方法不受影响,只是在子类中被覆盖)
SubType.prototype.getSuperValue = function (){
return false;
};
var instance = new SubType();
console.log(instance.getSuperValue()); // false
console.log(SuperType.prototype.getSuperValue); // ƒ (){ return this.property; }
var instance1 = new SubType();
var instance2 = new SubType();
console.log(instance1.colors); //["red", "blue", "green"]
console.log(instance2.colors);//["red", "blue", "green"]
instance1.colors.push("black");
console.log(instance1.colors);//["red", "blue", "green", "black"]
console.log(instance2.colors);//["red", "blue", "green", "black"]
instance1.property = 'test';
console.log(instance1.property);// 'test'
console.log(instance2.property);// true
优点:可以让所有对象实例共享它所包含的属性和方法(复用性)
缺点:
1.在创建子类型的实例时,不能向超类型的构造函数中传递参数。
2.如果包含引用类型值的属性,那一个实例改了这个属性(引用类型值),其他实例也跟着改变。
实际代码体验原型链
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
function Test() {
this.testproperty = true;
}
Test.prototype = new SubType();
var testInstance = new Test();
testInstance.__proto__ === Test.prototype // true
testInstance.__proto__.__proto__ === SubType.prototype // true
testInstance.__proto__.__proto__.__proto__ === SuperType.prototype // true
总结:一个实例的__proto__ 等于这个实例所属的构造函数的prototype
所以解释下下面为何为true?
function Point(x, y) {
this.x = x;
this.y = y;
}
var myPoint = new Point();
// the following are all true
myPoint.__proto__ == Point.prototype // true
myPoint.__proto__.__proto__ == Object.prototype // true
这边是因为myPoint.proto. 等于Point.prototype,而Point.prototype是Object的实例{}, 所以等于Object.prototype
借用构造函数(继承)
思路
:在子类型构造函数的内部调用超类型构造函数。
核心code
:
function SubType(){ //SubType继承了 SuperType
SuperType.call(this,argument);
}
实例
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){ //继承了 SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
console.log(instance2.colors); //"red,blue,green"
优点
: 1.子类型构造函数中可向超类型构造函数传递参数。
2.方法都在构造函数中定义,对于属性值是引用类型的就可通过在每个实例上重新创建一遍,避免所有实例的该属性值指向同一堆内存地址,一个改其他也跟着改。
缺点
:无法避免构造函数模式存在的问题——方法都在构造函数中定义(无法复用);在超类型的原型中定义的方法,对子类型而言也是不可见的。
组合继承
思路
:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
实例
function SuperType(name){ //父类(构造函数)
this.name = name;
}
SuperType.prototype.sayName = function(){ //父类的原型添加一个方法
console.log(this.name);
}
function SubType(name, age){ //借用构造函数来实现对实例属性的继承
SuperType.call(this, name); //继承实例属性 这边继承this.name = name;
this.age = age; //自己的属性
}
SubType.prototype = new SuperType(); //使用原型链实现对原型属性和方法的继承 这边是继承
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("xm", 22);
console.log(instance1.age) // 22
console.log(instance1.name) // xm
instance1.sayName(); //xm
instance1.sayAge(); //22
优缺点
:借用构造函数继承和原型链继承的取精去糟。