先看下方面试题,思考一下结果.
Object.prototype.test1 = function () {
console.log('test1');
};
Function.prototype.test2 = function () {
console.log('test2');
};
function Fun() {
this.a = 1;
}
var obj = new Fun();
console.log(obj.test1);
console.log(obj.test2);
答案:
obj.test1输出了一个函数,而obj.test2输出undefined.
为了解答这道题,需要深入理解javascript的原型链.
知识点
什么是js原型链
在学习原型链之前,先了解三个基本概念:构造函数,实例对象,原型对象
构造函数
构造函数也是一个函数,只不过它比普通函数多一个功能,使用new关键字调用时能创建对象.
function student(){
this.name = "张三";
}
var stu1 = new student(); //创建对象
console.log(stu1.name); //输出张三
上面定义的student函数就是一个构造函数,通过使用new关键字生成一个新对象
实例对象
实例对象是使用new关键字调用构造函数后返回的新对象.对应到上面代码中的stu1
原型对象
原型对象也是一个对象,它是构造函数的prototype指向的一个对象.
function student(){
this.name = "张三";
}
student.prototype.run = function(){console.log("奔跑");}
var stu1 = new student(); //创建对象
stu1.run() //打印'奔跑'
student.prototype就是原型对象,原型对象上面添加属性和方法后,实例对象就可以调用原型对象上面的属性和方法.
值得注意,Object,Function,Array,String,Number等都属于构造函数.通过使用new关键字可以创建相应的实例对象,另外在构造函数的prototype上面添加属性和方法,相应的实例对象就能调用.
实例对象可以访问原型对象上的属性和方法,而原型对象可能又继承于另外一个原型对象.这样第一层的实例对象可以访问第二层原型对象的属性和方法,也可以访问第三层原型对象的属性和方法.这样形成的链条就是原型链.下面通过图示来深入理解原型链的结构.
原型链的架构
上图中矩形是构造函数,椭圆是原型对象,带圆角的矩形是实例对象.
以上图中变量array为例,array继承了 Array的原型对象 , Array的原型对象 又继承了 Object原型对象 .此时array, Array的原型对象 和 Object的原型对象 构成了一条原型链.
原型链上的高层对象定义的属性和方法,低层对象都能调用和访问.
Object.prototype的地位
原型链的最高层是Object.prototype.所有对象都继承Object.prototype,倘若在Object.prototype上定义属性和方法,所有对象(null除外)都能继承使用.
Object.prototype.test1 = 123;
var obj = {};
console.log(obj.test1); //输出123
obj.test1 = 456;
console.log(obj.test1); //输出456,倘若子对象定义了与原型链上的对象相同的属性和方法时,子对象会覆盖
console.log(typeof null) // 输出 "object"
console.log(null.test1) //报错,null虽然也是对象,但是它不能调用任何属性和方法
构造函数和原型对象
Object,Function,Array,String,RegExp都是构造函数,通过.prototype的方式可以访问上图右侧的原型对象.
将上述原型对象打印输出,可以看到原型对象和普通对象没有区别,只不过本身定义了很多方法.
对象的继承
不管是实例对象还是原型对象都可以使用__proto__获取父级的原型对象.
var array = new Array();
array.__proto__ === Array.prototype; // true,通过__proto__属性可以获取父级的原型对象
Array.prototype.__proto__ === Object.prototype //true
//Object.prototype是所有对象的最高层,它再往一层就为null了
console.log(Object.prototype.__proto__) //输出 null
array是实例对象,它可以使用Array原型对象上所有属性和方法,也可以使用Object原型对象的属性和方法.
Object.prototype.test1 = 123;
Array.prototype.test2 = 456;
var array = new Array();
console.log(array.test1); //输出123
console.log(array.test2); //输出456
普通定义的对象和new出对象有何区别
很多时候定义对象喜欢直接使用var obj = {} ,var array = [], var fun = function(){},它们和new出来的对象有何区别.
var obj1 = {};
var obj2 = new Object();
obj1.__proto__ === obj2.__proto__ // 打印 true,都是指向了Object.prototype
var array1 = [];
var array2 = new Array();
array1.__proto__ === array2.__proto__ // 打印 true,都是指向了Array.prototype
从上面看出,使用var定义和new出来的对象没有区别,但有时使用new创建对象功能会更强大
var fun1 = function(){
console.log(123);
}
var code = "console.log(456)";
var fun2 = new Function(code);
fun2(); // 输出456
如果执行的代码是一段字符串,当使用构造函数Function创建函数对象时,就能动态改变函数体,达到了类似eval的效果.
Function和Object
从上面的叙述可知,Object.prototype是所有对象的祖先.Function.prototype的父级就是Object.prototype
Function.prototype.__proto__ === Object.prototype //输出 true
Function的原型对象和Object的原型对象关系理清楚了,接下里看构造函数Object和Function
Object是一个构造函数,同时它也是一个函数(函数本身就是个对象).只要是函数就会继承Function.prototype的属性和方法.
Function.prototype.test = "测试";
console.log(Object.test); //输出 "测试"
在 Function.prototype 上定义属性和方法,那么所有的函数都能继承调用.
Object和Object.prototype
Object.prototype.test = "测试";
console.log(Object.test); //输出 "测试"
在Object.prototype上面定义任何属性和方法,不光是Object, Function,Array,RegExp等都能继承调用.
以Object为例,Object是一个构造函数,同时也是一个函数对象.Object继承于Function.prototype,而Function.prototype又继承于Object.prototype.由此Object,Function.prototype和Object.prototype构成了一条原型链.
Object.__proto__.__proto__ === Object.prototype; //输出 true
总结
-
在分析原型链相关题目时,首先要根据上下文判端变量是作为构造函数还是函数对象而言,比如 Function 既可以作构造函数,也可以当做函数对象.当作为构造函数时才可以调用.prototype获取原型对象,当作为实例对象或原型对象时才可以调用__protp__寻找父级的原型对象.
Function.prototype === Function.__proto__ // 输出true
Function作为构造函数而言调用prototype 获取到了所有函数对象的父级.而Function作为普通函数而言,通过__proto__也能得到所有函数对象的父级.因此它们两个相等.
-
只有在一条原型链上的对象才构成继承关系.比如 Array.prototype.test = “测试”;var obj = {}; 此时obj就不能获取test属性.
原型链的应用
对象属性获取
var obj1 = {
a:1
};
var obj2 = {
b:2
};
var obj3 = {
c:3
}
obj1.__proto__ = obj2;
obj2.__proto__ = obj3;
console.log(obj1.c); //输出3
创建3个对象,通过改变__proto__使obj1,obj2和obj3以及 Object.prototype 形成了一条原型链.
当obj1获取c属性值时,会先从obj1中寻找,没找到跳到上一层obj2,仍然没找到继续跳上一层,最终在obj3中找到了c输出.
for in循环
var obj1 ={
a:1
}
var obj2 = {
b:2
};
obj1.__proto__ = obj2;
for(var key in obj1){
console.log(key); //输出 a b
}
虽然代码只对obj1做循环,但是for in循环的机制会将整条原型链上所有可枚举的属性和方法全部获取到.
有没有办法只找出对象自身定义的属性和方法,放弃原型链上的定义呢?(可以通过hasOwnProperty做到)
var obj1 ={
a:1
}
var obj2 = {
b:2
};
obj1.__proto__ = obj2;
for(var key in obj1){
if(obj1.hasOwnProperty(key)){
console.log(key); //只输出a
}
}
解题
Object.prototype.test1 = function () {
console.log('test1');
};
Function.prototype.test2 = function () {
console.log('test2');
};
function Fun() {
this.a = 1;
}
var obj = new Fun();
console.log(obj.test1);
console.log(obj.test2);
现在再来看最初的这道面试题.obj是一个普通对象,它的父级是Fun.prototype.
obj,Fun.prototype和Object.prototype构成了一条原型链,所以test1能获取,test2为undefined.
Function.prototype上定义了test2方法,可以让所有的函数对象都能获取test2,但普通对象不行.如下:
console.log(Fun.test2 ); //输出 test2函数