JS原型和原型链
2017-08-23 00:00
183 查看
##创建对象
使用Object构造函数或者对象字面量可以创建单个对象,但面对创建多个对象的时候,代码无法复用,这显然不是我们所期望的,为了解决此问题,人们发明了以下几种创建对象的方式
####工厂模式
使用工厂模式创建对象存在一个问题,就是无法识别对象。这才有了后面的构造函数模式
####构造函数模式
创建Person实例会经历4个步骤
创建一个新对象
将构造函数(Person)的作用域赋值给新对象
执行构造函数代码
返回新对象
使用这种方式能通过constructor 或者instanceof 来识别对象类型,但还存在一个问题,就是构造函数中的方法sayName()会在每次创建新实例时构建一次,也就是方法没办法复用。使用原型模式可以解决这个问题
####原型模式
每个函数创建时都有一个prototype属性,指向一个原型对象,这个原型对象包含所有实例共享的属性和方法。
还可以以自变量定义的形式定义Person.prototype,写法更为简洁
原型搜索机制:访问Person1的属性,首先从实例本身开始搜索,如果实例有该属性,则返回,如果没有,则继续搜索指针指向的原型对象,如果在原型对象中找到,返回属性值
原型模式也存在一定问题。默认情况下,所有实例的属性都是相同的,但这只是带来一些不便,最大的问题是属性的共享导致的,如果属性是引用类型,那么操作person1的属性会影响到person2的属性。
####组合使用构造函数模式和原型模式
构造函数模式适合定义私有的属性,而原型模式适合定义共有的属性和方法。组合使用能发挥各自的长处,这种模式也是应用最为广泛的
##继承
javascript中实现继承主要就是通过原形链来实现的。什么是原型链呢?
####原型链
了解原型链之前,我们先来回顾下构造函数、原型和实例之间的关系:每个构造函数在创建之初会有一个prototype(原型)属性,这个原型属性是一个指向构造函数的原型对象的指针,原型对象包含一个指向构造函数的指针constructor。然后调用构造函数创建一个实例,这个实例的内部又会包含一个指针(__proto__),这个指针指向构造函数的原型对象。上述中创建person1实例,在控制台可以看到如下信息。
假设有两个构造函数A()和B(),让B的原型对象等于A的实例,那么B的原型对象会包含一个指针(__proto__)指向A的原型对象,如果再有一个构造函数C(),让C的原型对象等于B的实例,如此层层递进,就构成了原型和实例之间的链条,也就是所谓的原型链
结合上文提到的原型搜索机制,访问instance.getSuperValue(),先搜索实例Sub,然后实例没有会去搜索Sub.prototype,仍然没有找到,会再去Super.prototype中查找,这才找到getSuperValue方法。在找不到属性或者方法的情况下,搜索过程会沿着原型链一环一环前进,直到末端才会停下。
####原型链的问题
原型链很强大,可以实现javascript的继承,但它存在一些问题,就如同上文中使用原型模式创建对象遇到的问题一样。
省略了为构造函数传递参数这一环节,导致所有Sub实例初始的属性都一样
如果属性是引用类型,那么所有的Sub实例会共享属性
####借用构造函数
在sub的构造函数中调用sup的构造函数,这样每个sub的实例就会拥有sup的属性。但是借用构造函数有一个问题,在使用构造函数创建对象也遇到过:不论是在sub中定义的函数还是sup中定义的函数无法复用。
###组合继承
将原型链和借用构造函数结合使用,发挥各自的长处,成为javascript中最常见的继承模式
使用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();