1、原型:
_proto_:每个对象数据类型(普通对象、实例、prototype....)天生带一个属性_proto_,属性值是当前实例所属类的原型prototype。
prototype:每个函数数据类型(普通函数、类)都有一个天生的自带属性prototype,属性值是一个对象数据类型的值。
_proto_与prototype的关系:
_proto_连接了构造函数与构造函数的实例,当访问实例的属性和方法时,如果实例上没有这个属性或方法,那么就会通过_proto_查找_proto_指向的构造函数的prototype上的属性,看prototype上是否有相应的属性。_proto_与prototype都可以被称为原型,原型也是对象,但是更多人认为_proto_是浏览器系统使用的,不是标准的原型;prototype是程序员使用的,是标准的原型,_proto_在某些浏览器不存在,例如ie低版本。_proto_是实例对象的一个属性,prototype是构造函数的一个属性,同时它们也是一个对象。如图:
_proto_、prototype与构造函数的关系图:
原型的注意点:
(1)这并不是对构造函数原型属性的更改,而是直接该改变引用地址。
(2)对构造函数原型的更改需要注意执行顺序:
此时person.name=cherry,为什么会有这样的结果?运行机制为:
在new创建实例时,函数内部产生了this对象:
function Person() {
var this = { _proto_ : Person.prototype };
return this;//这两步都是隐形的浏览器默认的,程序员定义的属性都赋值给this,this就是构造函数的一个实例,这也是为什么构造函数使用this定义属性
}
上面的例子是在实例创建之前改变构造函数的原型,所以实例的原型对象_proto_指向的地址就是改变的构造函数原型地址
原型的作用:
1、实现数据共享,减少内存空间 ;
2、实现继承
2、原型链:
如下图这样,通过实例的_proto_原型连接相应的构造函数的prototype原型对象,直至到达Object.prototype顶级原型对象(Object是js中所有对象数据类型的基类),这种呈链式结构的指向称为原型链。
原型链的注意点:
(1)绝大多数的对象最终都会继承自Object.prototype,也就是说构造函数创建的实例原型链的顶端为Object.prototype;
(2)通过Object.create(原型)创建自定义prototype原型对象,同时这个语句参数可以为null,null为实例的原型,null没有原型,此时对象最终不会继承自Object.prototype,这也就是null不可以使用toString等方法的原因。
通过原型链批量设置公有属性和方法:
(1)简写类的prototype,即是通过将函数的prototype的引用赋值给一个变量,这样来简化代码。
(2)重构方式(改变原型的指向):
//普通函数重构方式
function Person(name){ this.name = name }
/*这种方式改变了原型的指向,不单单是更改原型的属性或方法;这种方式使的Person.prototype原型对象没有constructor属性,需要我们手动设置
Person.prototype = { name:"hello" };
var person = new Person();
console.log(person.name) ——> hello
//内置类重构方式
String.prototype = { };
/*用这种类似普通函数改变原型指向的方式来改变内置类的原型指向浏览器会自动忽略,只能手动逐个修改内置类的原型的属性和方法 ,如果修改的属性或方法与内置的属性或方法同名,那么内置的会被替换 */
函数特别之处:
1、函数既有_proto_也有prototype,所以可以说函数既是函数也是对象。不过函数的_proto_构造器constructor指向Function,也就是说所有函数都是Function的构造函数创建出来的实例对象,而Function也是对象,它的_proto_构造器constructor指向Object,即有如下关系:
Function实例fn的原型——>fn._proto_ == Function.prototype
Function.prototype._proto_ == Object.prototype
//这个原型关系需要和构造函数原型链区分好
2、函数具有三种角色:
3、调用函数运行过程:
var arr = [ 1,2,3 ];
arr.slice(); //首先,进行arr.slice查找自身和原型链上的slice方法,然后 arr.slice() 进行slice函数执行