Javascript面向对象的程序设计

最近由于在给一个小同窗作学习计划,因此也记录一些知识点,便于后面的同窗的学习交流。这篇文章是关于Javascript的面向对象的程序设计,主要从三个方面来介绍,1. 理解对象属性; 2. 理解并建立对象; 3. 理解继承函数

1、理解对象属性
首先咱们来理解Javascript对象是什么?在Javascript中,万物皆对象。其中建立自定义对象的最简单的方式就是建立一个Object的实例,以下:性能

var person = new Object();
person.age = 29;

// 对象字面量的形式:
var person = {
     age: 29
};

ECMAScript中有两种属性:数据属性和访问器属性。学习

数据属性:
其中数据属性有四个描述其行为的特性:
Configurable: 表示能都经过delete删除属性从而从新定义属性。
Enumerable: 表示可否经过for in 循环返回属性。
Writable: 表示可否修改属性的值。
Value: 包含这个属性的数据值。
要修改属性默认的配置,必须使用Object.defineProperty(), 这个方法接收三个参数:属性所在的对象,属性的名字和一个描述性对象。
好比:this

var person = {};
Object.defineProperty(person, ’name’, {
      writable: false,
      value: ’Nicholas'
});

alert(person.name); //Nicholas
person.name = ‘Greg’;
alert(person.name); //Nicholas

访问器属性:
访问器属性包含一对setter和getter函数。包含以下4个特性:
Configurable:可否被delete删除属性从新定义。默认值:true
Enumerable:可否被for-in枚举。默认值:true
Get:读取属性值。默认值:undefined
Set:写入属性值。默认值:undefinedprototype

var dog = {
    _age: 2,
    weight: 10
}

Object.defineProperty(dog, 'age', {
    get: function () {
        return this._age
    },
    set: function (newVal) {
        this._age = newVal
        this.weight += 1
    }
})

知道了对象的属性,那么咱们建立对象的方式是什么呢?设计

2、建立对象的方式指针

建立对象的方式一般有下面几种方式:
一、工厂模式
咱们举个例子:code

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 person = 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 person = new Person(‘Greg’, 27, ‘Doctor’);**

构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。这里要提的一个属性是Constructor, 每一个new 出来的实例都有一个Constructor(构造函数)属性,该属性指向构造函数。
对象的Constructor属性最初是用来标识对象类型的。可是,提到检测对象类型,仍是instanceof操做符更好可靠一些。
alert(person1 instanceof Object); //true

构造函数的问题问题就是,每一个方法都要在每一个实例上从新建立一遍,固然,能够把函数定义转移到构造函数外部来解决这个问题,以下实例:

**function Person(name, age, job) {

this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;

}
function sayName() {

alert(this.name);

}
var person = new Person(‘Greg’, 27, ‘Doctor’);**

那么这里的问题就是:在全局做用域中定义的函数实际上被只能被某个对象调用,这让全局做用域有点名存实亡。而更让人没法接受的是:若是独享须要定义不少方法,那么就要定义不少个全局函数。下面咱们来看看原型模式是不能解决这个问题。

三、原型模式
prototype就是经过调用构造函数而建立的那个对象实例的原型对象。咱们来看下面一个例子来理解这句话:

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”

与构造函数模式不一样的是,新对象的这些属性和方法是由全部实例共享的。
针对这个特性,咱们要注意的几个点就是。
第一,若是实例的属性与方法与原型的属性和方法同名,那谁的优先级高呢?
固然是建立出来的实例的属性和方法的优先级高。
第二,实例的属性与方法的修改会影响原型同名的属性和方法吗?
不会
这里提一下hasOwnProperty(), 使用hasOwnProperty()方法能够检测一个属性是存在于实例中,仍是存在原型中。
第三,如何判断某个属性是存在原型中?

function hasPrototypeProperty(object, name) {
    return !Object.hasOwnProperty(name) && (name in object);
}

hasOwnProperty()只在属性存在于实例中才返回true, 所以只要in操做符返回true而hasOwnProperty()返回false, 就能够肯定属性是原型中的属性、这里说下Constructor, 每建立一个函数,就会同时建立它的Prototype对象,这个对象也会自动得到Constructor属性。
咱们来看一个例子:

function Person() {

}

Person.prototype = {
    constructor: Person,
    name: “Nichloas”,
    age: 29,
    job: “Software Engineer”,
    sayName: function () {
         alert(this.name);
    }  
};

以上代码特地包含了一个Constructor属性,并将它的值设置为Person, 从而确保了经过该属性可以访问到适当的值。可是,以这种方式重设Constructor属性会致使它的Enumerable特性被设置为true, 默认状况下原生的Constructor属性是不可枚举的。

Object.defineProperty(Person.prototype, “Constructor”, {
    enumerable: false,
    value: Person
});

原型对象的问题:
首先,它省略了为构造函数传递初始化参数这一环节。
而后,原型中全部属性是被不少实例共享的,对于包含引用类型值的属性来讲,问题比较突出。因此,使用最多的方式使用构造函数和原型模式

四、组合使用构造函数模式和原型模式

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 person = new Person(‘Greg’, 27, ‘Doctor’);

知道了建立对象的方式,那么在Javascript中咱们如何来继承对象呢?

3、继承
一、原型链
构造函数,原型和实例的关系:每个构造函数都也有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如咱们让原型对象等于另外一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另外一个原型的指针,相应的,另外一个原型中也包含也包含着一个指向一个构造函数的指针,假如另外一个原型又是另外一个类型的实例,那么上诉关系依然成立,咱们来看下面一个例子.

function SuperType() {
    this.property = true;
}

SuerType.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); // true
这个例子中,SubType继承了SuperType,而SuperType继承了Object。若是要肯定原型和实例的关系,能够用instanceOf操做符与isPropertyOF()方法。
如 instance instanceOf object; // true
Object.prototype isPropertype(instance); // true

可是经过原型链实现继承时,不能使用对象字面量建立原型方法。

function SuperType() {
    this.property = true;
}

SuerType.prototype.getSuperValue = function () {
     return this.property;
};

function SubType() {
    This.subProperty = false;
}

// 继承了SuperType
SubType.prototype = new SuperType();

// 使用字面量添加新方法,会致使上一行代码无效
SubType.prototype = {
      getSubValue: function () {
            return this.subproperty;
       },
      someOtherMethod: function () {
            return false;
      }
};

var instance = new SubType();
alert(instance.getSuperValue); // error

接下来,咱们来讲下原型链继承的问题:
第一,最主要的问题来自包含引用类型值的原型。包含引用类型值的原型属性会被全部实例共享,而这也正是为何要在构造函数中,而不是在原型对象中定义属性的缘由。
第二,在建立子类型的实例时候,不能向超类型的构造函数中传递参数。

二、借用构造函数

function SuperType(name) {
       this.name = name;
}

function SubType() {
    // 继承了SuperType, 同时还传递了参数
    SuperType.call(this, “Nicholas”);
    this.age = 29;
}

var instance = new SubType();
alert(instance.name);  // “Nicholas”

借用构造函数的问题,方法都在构造函数中定义,所以函数复用就无从谈起了。并且,在超类型的原型中定义的方法,对于子类型而言也是不可见的,结果全部类型都只能使用构造函数模式,考虑到这些问题,借用构造函数的技术也是不多单独使用的。

三、组合继承
组合继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥两者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和放大的绩效,而经过借用构造函数来实现对实例属性的继承。

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;
} 

// 继承方法
SubType.prototype = new SuperType();

SubType.prototype.sayAge = function () {
    alert(this.age);
};


var instance = new SubType(“Nicholas”, 29);
instance.colors.push(“black”);
alert(instance.colors); // red, blue, green, black

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优势,成为Javascript中最多见的继承模式。

四、原型式继承

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
]

var person = {
    Name: “Nicholas”,
    friends: [“Shelby”, “Court”, “Van"]
};

var anotherPerson = object(person);
anotherPerson.name = “Greg”;

ECMAScipt5经过新增Object.create()方法规范化了原型式继承。
接着上面的例子:

var anotherPerson = Object.create(person);
anotherPerson.name = “Greg”;

五、寄生式继承
寄生式继承是与原型式继承紧密相关的一种思路,它与工厂模式相似,即建立一个仅用于封装过程的函数,该函数在内部以某种方法来加强对象,最后再像真地是它作了全部工做同样返回对象。
例如:

function createAnother(original) {
     var clone = object(original);
     clone.sayHi = function () {
           alert(‘hi’);
     };
     return clone; 
}

var person = {
    name: ’Nochplas’,
    friends: [’Shelby’, ‘Court’, ‘Van']
};

var anotherPerson = createAnother(person);

六、组合寄生式继承

组合继承是Javascript最经常使用的继承模式,不过,组合继承最大的问题就是不管什么状况下,都会调用两次超类型构建函数:一次是在建立子类型原型的时候,另外一次是在子类型构造函数内部。

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);   // 第二次调用 SuperType()
     this.age = age;
} 

// 继承方法
SubType.prototype = new SuperType(); // 第一次调用 SuperType()

SubType.prototype.sayAge = function () {
    alert(this.age);
};

所谓寄生组合式继承,即经过借用构造函数来继承属性,经过原型链的混成形式来继承方法。没必要为了制定子类型的原型而调用超类型的构造函数,咱们所须要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,而后再将结果制定给子类型的原型。以下:

function inheritPrototype (subType, superType) {
   var prototype = object (superType.prototype); // 前面的原型式继承object方法
   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);
};
相关文章
相关标签/搜索