js原型、原型链
1.函数也是对象
有时我们会好奇为什么能给一个函数添加属性,函数难道不应该就是一个执行过程的作用域吗?
var fn=function(){
this.name='手机'
}
fn.age=11
console.log(fn.age) //11
console.log(fn)
// function(){
// this.name='手机'
// }
其实,在JS里,函数就是一个对象,对象里面保存键值对。本例中fn为属性,属性值是函数的字符串,当调用函数执行时,js会将该字符串解析成可执行语句。
2.构造函数和普通函数的区别
function fn(){
this.name='手机'
console.log(this)
}
fn()
//此时这里面的this表示window
var m=new fn()
//此时this表示m对象。
- 用关键词new 调用的函数叫做构造函数
- 虽然没有硬性规定,为了和普通函数区分,构造函数一般首字母大写。
- 构造函数里的this指向通过构造函数创建出来的新对象。
3.函数和原型的关系
function Fn(name){
this.name=name
this.kind='电子产品'
}
var m1=new Fn('手机');
var m2=new Fn('电脑');
结果会生成两个m1,m2对象。
m1={
name:'手机',
kind:'电子产品'
}
m2={
name:'电脑',
kind:'电子产品'
}
//每一个实例对象,都有自己的属性和方法的副本。修改任何一个都不会影响另一个。
这不仅无法做到数据共享,也是极大的资源浪费。
function Fn(name){
this.name=name
}
Fn.prototype = { kind : '电子产品' };
var m1=new Fn('手机');
var m2=new Fn('电脑');
结果会生成两个m1,m2对象。
m1={
name:'手机',
kind:'电子产品'
}
m2={
name:'电脑',
kind:'电子产品'
}
//kind属性放在prototype对象里,是两个实例对象共享的。只要修改了prototype对象,
就会同时影响到两个实例对象。
- JS里,我们创建的每一个函数都有一个prototype(原型)属性,这个属性指一个——用于包含该对象所有实例的共享属性和方法——的对象。
- 原型对象同时包含一个指针指向这个这个函数,这个指针就是constructor,这个函数也就是构造函数。
了解了原型和constructor指针后,我们通过一个例子以及一张图来进一步了解这两者的关系。
function Person(){
this.age=10
}
Person.prototype.name = 'Nicholas';
Person.prototype.age = 24;
Person.prototype.sayAge = function () {
alert(this.age);
};
1.png
4.原型和实例的关系
function Person(){
this.age=10
}
Person.prototype.name = 'Nicholas';
Person.prototype.age = 24;
Person.prototype.sayAge = function () {
alert(this.age);
};
//实例
var person1 = new Person('Lee');
var person2 = new Person('Lucy');
我们新建了两个实例person1和person2,这些实例的内部都会包含一个指向其构造函数的原型对象的指针(内部属性),不是对外访问的API,这个指针叫[[Prototype]],在ES5的标准上没有规定访问这个属性,所以在浏览器上是不能console出这个属性的,会报错。但是大部分浏览器通过proto的属性来访问它,成为了实际的通用属性,于是在ES6的附录里写进了该属性。
prototype和proto都叫原型,如果非要区分,prototype我们称为显示原型,proto我们称为隐式原型。
22.pngperson1.__proto__ == Person.prototype // true
4.原型链
function Person(){
this.age=10
}
Person.prototype.name = 'Nicholas';
Person.prototype.age = 24;
Person.prototype.sayAge = function () {
alert(this.age);
};
//实例
var person1 = new Person();
前面的demo中我们举了一个类似这样的例子。
我们尝试输出实例的属性。
person1.age; // 10
person1.name; // Nicholas
person1.toString; // function toString() { [native code] }
person1
我们输出一下person1试试
im.jpg从上例中,我们可以看出,Person有自己的原型,即Person.prototype,同样Person.prototype也有自己的原型,即Person.prototype.proto属性,我们输出下试试。
console.log(Person.prototype)
console.log(Person.prototype.__proto__)
console.log(Person.prototype.__proto__.__proto__)
结果如下。
img1.jpg我们来总结下原型链。
当我们访问实例对象的一个属性时候,如果不存在,就去实例的原型对象里面找,这个原型对象也有自己的原型对象。就这样一直找就构成了我们常说的线性原型链。
上面代码的age来自于自身属性,name来自于原型属性,toString( )方法来自于Person原型对象的原型Object的原型。当我们访问一个实例属性的时候,如果没有找到,我们就会继续搜索实例的原型,如果还没有找到,就递归搜索原型链直到原型链末端。
我们来验证下
Person.prototype.__proto__ == Object.prototype // true
继续深入验证
Person.__proto__ == Function.prototype // true
Function.prototype.__proto__ == Object.prototype // true
我们会发现Person是Function对象的实例,Function是Object对象的实例,Person原型是Object对象的实例。
最后,奉上一张原型大图
33.jpg- 构造函数和对象原型一一对应,他们与实例一起作为三要素构成了三面这幅图。最左侧是实例,中间是构造函数,最右侧是对象原型。
- 最最右侧的null告诉我们:Object.prototype.proto = null,也就是Object.prototype是JS中一切对象的根源。其余的对象继承于它,并拥有自己的方法和属性。
最后的最后,说了这么多,实际怎么用?大家下去可以看一下,js面向对象的三大特性:
封装、继承、多态
原型链主要用于继承特性。
参考来源https://segmentfault.com/a/1190000011389965