您的位置:首页 > Web前端 > JavaScript

JS原型和原型链

2017-08-23 00:00 183 查看
##创建对象
使用Object构造函数或者对象字面量可以创建单个对象,但面对创建多个对象的时候,代码无法复用,这显然不是我们所期望的,为了解决此问题,人们发明了以下几种创建对象的方式
####工厂模式

function createPerson(name, age) {
var o = {};
o.name = name;
o.age = age;
o.sayName = function() {
console.log(this.name);
}
return o;
}
var person1 = createPerson('Tom', 27);
person1.sayName();
var person2 = createPerson('Jack', 28);
person2.sayName();

使用工厂模式创建对象存在一个问题,就是无法识别对象。这才有了后面的构造函数模式
####构造函数模式

function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = function() {
console.log(this.name);
}
}
var person1 = new Person('Tom', 27);
person1.sayName();
var person2 = new Person('Jack', 28);
person2.sayName();

console.log(person1.constructor == Person);
console.log(person1 instanceof Person);

console.log(person1.sayName == person2.sayName);

创建Person实例会经历4个步骤

创建一个新对象

将构造函数(Person)的作用域赋值给新对象

执行构造函数代码

返回新对象

使用这种方式能通过constructor 或者instanceof 来识别对象类型,但还存在一个问题,就是构造函数中的方法sayName()会在每次创建新实例时构建一次,也就是方法没办法复用。使用原型模式可以解决这个问题
####原型模式
每个函数创建时都有一个prototype属性,指向一个原型对象,这个原型对象包含所有实例共享的属性和方法。

function Person() {

}
Person.prototype.name = 'Tom';
Person.prototype.age = 27;
Person.prototype.sayName = function() {
console.log(this.name);
}
person1 = new Person();
person1.sayName();
person2 = new Person();

console.log(person1.sayName == person2.sayName);

还可以以自变量定义的形式定义Person.prototype,写法更为简洁

function Person() {

}
Person.prototype = {
name: 'Tom',
age: 27,
sayName: function() {
console.log(this.name);
}
};
person1 = new Person();
person1.sayName();
person2 = new Person();

console.log(person1.sayName == person2.sayName);

原型搜索机制:访问Person1的属性,首先从实例本身开始搜索,如果实例有该属性,则返回,如果没有,则继续搜索指针指向的原型对象,如果在原型对象中找到,返回属性值

原型模式也存在一定问题。默认情况下,所有实例的属性都是相同的,但这只是带来一些不便,最大的问题是属性的共享导致的,如果属性是引用类型,那么操作person1的属性会影响到person2的属性。
####组合使用构造函数模式和原型模式
构造函数模式适合定义私有的属性,而原型模式适合定义共有的属性和方法。组合使用能发挥各自的长处,这种模式也是应用最为广泛的

function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function() {
console.log(this.name);
}
person1 = new Person('Tom', 27);
person1.sayName();
person2 = new Person('Jack', 28);
person2.sayName();
console.log(person1.sayName == person2.sayName);

##继承
javascript中实现继承主要就是通过原形链来实现的。什么是原型链呢?
####原型链
了解原型链之前,我们先来回顾下构造函数、原型和实例之间的关系:每个构造函数在创建之初会有一个prototype(原型)属性,这个原型属性是一个指向构造函数的原型对象的指针,原型对象包含一个指向构造函数的指针constructor。然后调用构造函数创建一个实例,这个实例的内部又会包含一个指针(__proto__),这个指针指向构造函数的原型对象。上述中创建person1实例,在控制台可以看到如下信息。



假设有两个构造函数A()和B(),让B的原型对象等于A的实例,那么B的原型对象会包含一个指针(__proto__)指向A的原型对象,如果再有一个构造函数C(),让C的原型对象等于B的实例,如此层层递进,就构成了原型和实例之间的链条,也就是所谓的原型链

function Super() {
this.superproperty = true;
}
Super.prototype.getSuperValue = function() {
console.log(this.superproperty);
}
function Sub() {
this.subproperty = false;
}
// 继承Super
Sub.prototype = new Super();
Sub.prototype.getSubValue = function() {
console.log(this.subproperty);
}
var instance = new Sub();
instance.getSuperValue();

结合上文提到的原型搜索机制,访问instance.getSuperValue(),先搜索实例Sub,然后实例没有会去搜索Sub.prototype,仍然没有找到,会再去Super.prototype中查找,这才找到getSuperValue方法。在找不到属性或者方法的情况下,搜索过程会沿着原型链一环一环前进,直到末端才会停下。
####原型链的问题
原型链很强大,可以实现javascript的继承,但它存在一些问题,就如同上文中使用原型模式创建对象遇到的问题一样。

省略了为构造函数传递参数这一环节,导致所有Sub实例初始的属性都一样

如果属性是引用类型,那么所有的Sub实例会共享属性
####借用构造函数

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

function Sub() {
// 继承Sup并且传递参数
Super.call(this, 'Tom');
// 实例属性
this.age = 27;
}
var instance = new Sub();
console.log(instance.name, instance.age);

在sub的构造函数中调用sup的构造函数,这样每个sub的实例就会拥有sup的属性。但是借用构造函数有一个问题,在使用构造函数创建对象也遇到过:不论是在sub中定义的函数还是sup中定义的函数无法复用。
###组合继承
将原型链和借用构造函数结合使用,发挥各自的长处,成为javascript中最常见的继承模式

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

function Sub(name, age) {
// 继承Sup属性
Super.call(this, name);

this.age = age;
}
Sub.prototype = new Super();
Sub.prototype.sayName = function() {
console.log(this.age);
}
var instance = new Sub('Tom', 27);
instance.sayName();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: