JS面向对象设计模式

360 阅读4分钟

创建对象

工厂模式

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("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

但这种方式有一个缺点:无法判断某个对象是什么类型。

构造函数模式

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    };
}
var p1 = new Person("Nicholas", 29, "Software Engineer");
var p2 = new Person("Greg", 27, "Doctor");

构造函数也存在问题,每个方法都要在实例上创建一遍。也就是说p1和p2的sayName()方法虽然作用相同,但这两个方法并不是同一个函数

解析一道题

new操作符做了什么:

(1)创建一个新对象

(2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象)

(3)执行构造函数中的代码

(4)返回新对象

原型模式

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"

当我们改变 值为引用类型的对象的属性 时,这个改变的结果会被其他对象共享。

构造函数 + 原型模式

构造函数模式的属性没毛病。缺点是:无法共享方法

原型模式的方法没毛病。缺点是:当原形对象的属性的值为引用类型时,对其进行修改会反映到所有实例中 

那我们就将两者的结合,对象的属性使用构造函数模式创建,方法则使用原型模式创建

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
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("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

继承

原型链

JavaScript中引入了原型链的概念,具体思想: 子构造函数的原型对象初始化为父构造函数的实例,孙构造函数的原型对象初始化为子构造函数的实例…… ,这样子对象就可以通过原型链一级一级向上查找,访问父构造函数中的属性以及方法。

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());

当我们改变 值为引用类型的原型对象的属性 时,这个改变的结果会被所有子对象共享。这个缺点某些时候相当致命,所以我们很少使用这种方法来继承

借用构造函数

function SuperObject(){
  this.colors = ['red','blue'];
  this.sayBye= function(){
    console.log('Bye')
  }
}
function SubObject(){
  SuperObject.call(this);  // 在子类中调用父类的构造方法,实际上子类和父类已经没有上下级关系了
}
​
​
var instance1 = new SubObject();
instance1.colors.push('yellow');
var instance2 = new SubObject();
console.log(instance2.colors); //['red','blue']
console.log(instance2 instanceof SuperObject); // false
console.log(instance1.sayBye === instance2.sayBye)  // false

这个方法虽然弥补了原型链的缺点,但是又暴露出了新的缺点:

 1 子类和父类没有上下级关系,instance2 instanceof SuperObject 结果是false

 2 父类中的方法在每个子类中都会生成一遍,父类中的方法没有被复用。

组合继承

组合继承就是将原型链继承和借用构造方法继承组合,发挥两者之长。

function SuperType(name){
    this.name = name;
    this.colors = ['blue'];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
}
​
function SubType(name, age){
    // 引用父类型的属性,又调用了一次父函数
    SuperType.call(this,name);
    this.age = age;
}
// 继承父类型的方法,调用了一次父函数
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
}
​
var instance1 = new SubType('zz',18);
instance1.sayName(); //zz
instance1.sayAge(); //18
instance1.colors.push('red');
console.log(instance1.colors); // ['blue','red']
​
var instance2 = new SubType('tt',22);
console.log(instance2.colors); // ['blue']
组合继承会调用两次父类型函数

原型式继承
代码块
function object(o){
    function F(){};
    F.prototype = o;
    return new F();
}
​
var Person = {
    name: 'Nicholas'
    friends: ['zz','cc']
}
var anotherPerson = object(Person);
anotherPerson.friends.push('dd');
console.log(anotherPerson.friends); //['zz','cc','dd']

寄生式继承

function createAnother(o){
    var clone = object(o);
    o.sayHi = function(){
        console.log('hi');
    }
    return clone;
}

寄生组合式继承

虽然组合继承没啥大缺点,但是爱搞事情的有强迫症的程序猿们觉得,组合继承会调用两次父类型函数(在上面的代码中标注了),不够完美。于是道格拉斯就提出了寄生组合继承。

思路是构造一个中间函数,将中间函数的prototype指向父函数的原型对象,将子函数的prototype指向中间函数,并将中间函数的constructor属性指向子函数。

function object(o){
    function F(){};
    F.prototype = o;
    return new F();
}
​
function inheritPrototype(subType,superType){
    var prototype = object(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}
​
function SuperType(name){
    this.name = name;
    this.colors = ['red','blue','green'];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
}
function SubType(name,age){
    SuperType.call(this,name);
    this.age = age;
}
inheritPrototype(SubType,SuperType);
​
SubType.prototype.sayAge = function(){
    alert(this.age)
}
var instance1 = new SubType('zz', 18);
instance1.sayName();

ES6的继承

这段代码有两条原型链

参考资料

MDN:developer.mozilla.org/zh-CN/docs/…

JS高级语言程序设计第六章(面向对象的程序设计)

ES6(类):es6.ruanyifeng.com/#docs/class