1,原型介绍
要介绍原型,要就必须要从js的函数开始介绍了,在JavaScript中,函数本身也是一个包含了方法和属性的对象,而函数对象中一个属性就是prototype。下面我们逐步来了解它。
现有构造函数如下:
function Person(name, age){
this.name = name;
this.age = age;
this.sayHi = function(){
console.log("你好");
}
}
调用该构造函数创建对象,并对比创建出来的对象的sayHi
方法:
var p = new Person("张三", 18);
var p1 = new Person("李四", 19);
console.log(p.sayHi == p1.sayHi); //输出结果为false
由于每个对象都是由new Person
创建出来的,因此每创建一个对象,函数sayHi都会被重新创建一次,这个时候,每个对象都拥有一个独立的,但是功能完全相同的方法。
但其实功能相同的方法,我们没必要让他存在那么份,因为毕竟每一份,都会占用内存,这样就造成了资源浪费,一般我们可能会
想到使用外部的函数,然后大家都引用他,这样就都是一份,虽然解决了,但是这样造成了全局变量增多,代码的结构混乱,所以
也不是很好的方法,我们看下面的代码
- 每一个函数在定义的时候,都会有跟它关联的一个对象被创建出来
- 每一个由构造函数创建出来的对象,都会默认的和构造函数的神秘对象关联
- 当使用一个方法进行属性或者方法访问的时候,会先在当前对象内查找该属性和方法
- 如果当前对象内未找到,就回去跟它关联的神秘对象内进行查找
function Person(name, age){
this.name = name;
this.age = age;
this.sayHi = function(){
console.log("Hello!");
};
}
var p = new Person("张三", 18);
p.sayHi(); //当前对象内有这个方法,所以不会去神秘对象内进行查找
var p1 = new Person("李四", 19);
p1.sayHello(); //当前对象没没有找到这个方法,所以去神秘对象内进行查找
问题来了,如何访问到这个神秘对象呢?
//可以通过 构造函数.prototype 访问这个神秘对象
console.log(Person.prototype);
当尝试给这个对象新增一个方法之后:
Person.prototype.sayHello = function(){
console.log("我是神秘对象中的方法");
};
使用p
,p1
都可以访问这个方法:
p.sayHello();
p1.sayHello();
总结:
所有对象共享神秘对象(构造函数.prototype)内的属性和方法。
既然所有对象共享神秘对象(构造函数.prototype)内的属性和方法。我们只需要将需要共享的东西,也就是重复占用内存的东西,全部都放到 神秘对象(构造函数.prototype)中,那么所有对象就都可以使用,并且内存里面也只有一份了。
- 构造函数创建的对象 继承自 构造函数的原型属性
- 构造函数创建的对象 继承自 该对象的原型对象
- 原型中的成员, 可以直接被实例对象所使用
- 实例对象直接 "含有" 原型中的成员
- 因此实例对象 继承自 原型
- 这样的继承就是 "原型继承"
2.继承的实现方式
2.1. 最简单的继承实现
直接遍历父对象的属性,将所有的属性加到当前对象上
var animal = {
name:"Animal",
sex:"male",
age:5,
bark:function(){
console.log("Animal bark");
}
};
var dog = {};
for (var k in animal){
dog[k]= animal[k];
}
2.2. 原型继承
每一个构造函数都有prototype
原型属性,通过构造函数创建出来的对象都继承自该原型属性。所以可以通过更改构造函数的原型属性来实现继承。
function Dog(){
this.type = "yellow Dog";
}
function extend(obj1, obj2){
for (var k in obj2){
obj1[k] = obj2[k];
}
};
//使用混入的方式,将属性和方法添加到构造函数的原型属性上,构造函数所创建出来的实例就都有了这些属性和方法。
extend(Dog.prototype, {
name:"",
age:"",
sex:"",
bark:function(){}
})
//使用面向对象的思想把extend方法重新封装
//extend是扩展的意思,谁要扩展就主动调用extend这个方法
//所以extend应该是对象的方法,那现在我们要扩展的是构造函数的原型对象
//所以给构造函数的原型对象添加一个extend方法
//如下:
Dog.prototype.extend = function(obj){
for (var k in obj){
this[k]=obj[k];
}
}
//调用方式就变成了下面这种形式
Dog.prototype.extend({
name:"",
age:"",
sex:"",
bark:function(){}
});
3.属性搜索机制
属性搜索原则
访问一个对象的成员的时候,首先是在实例中找,没有找到, 就去原型中找, 但是原型中没有怎么办?
原型链
每一个对象都有原型属性,那么对象的原型属性也会有原型属性,所以这样就形成了一个链式结构,我们称之为原型链。
属性搜索原则
所谓的属性搜索原则,也就是属性的查找顺序,在访问对象的成员的时候,会遵循如下的原则:
-
首先在当前对象中查找,如果找到,停止查找,直接使用,如果没有找到,继续下一步
-
在该对象的原型中查找,如果找到,停止查找,直接使用,如果没有找到,继续下一步
-
在该对象的原型的原型中查找,如果找到,停止查找,直接使用,如果没有找到,继续下一步。
-
继续往上查找,直到查找到
Object.prototype
还没有, 那么是属性就返回undefied
,是方法,就报错xxx is not a function
。
4.原型链
4.1原型链结构
凡是对象就有原型, 原型又是对象, 因此凡是给定义一个对象, 那么就可以找到他的原型, 原型还有原型. 那么如此下去, 就构成一个对象的序列. 称该结构为原型链.
使用构造函数创建出对象, 并且没有利用赋值的方式修改原型, 就说该对象保留默认的原型链.
默认原型链结构是什么样子呢?
function Person() {
}
var p = new Person();
// p 具有默认的原型链
默认的原型链结构就是:
当前对象 -> 构造函数.prototype -> Object.prototype -> null
在实现继承的时候, 有时会利用替换原型链结构的方式实现原型继承, 那么原型链结构就会发生改变
function ItcastCollection () {
}
ItcastCollection.prototype = [];
var arr = new ItcastCollection();
// arr -> [] -> Array.prototype -> Object.prototype -> null
// var arr = new Array();
4.2原型式继承
观察:DOM对象的原型链
原型式继承就是利用修改原型链的结构( 增加一个节点, 删除一个节点, 修改节点中的成员 ), 来使得实例对象可以使用整条链中的所有成员.
绘制原型链结构
注意:函数也有__proto__
属性,暂时不考虑这个!
观察如下代码,绘制相应的原型链结构图:
function Person(){};
var p = new Person();
对应的原型链结构图为: