aermin/blog

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属性指向函数的原型对象,从而在原型对象添加所有实例可共享的属性和方法。

原型图

image

实例

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,数组会如实地一个实例改,其他跟着改;而函数却一个实例改,其他没跟着改。有知道答案的麻烦一定要告诉我下😂

优缺点:构造函数模式和原型模式的取精去糟。

继承

原型链(继承)

思路:利用原型让一个引用类型继承另一个引用类型的属性和方法。

原型链图

image

我画成这样更好理解

image

核心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

优缺点:借用构造函数继承和原型链继承的取精去糟。