Bootstrap

Object.create()是怎样建立原型链的

1、实例、原型之间的关系与判断方法

每个函数都有一个prototype属性,这个属性指向函数原型

函数原型自带一个constructor属性,这个属性指向函数自己。

举个例子,比如有这么一个函数

function Book(title,author){
	this.title= title;
	this.author= author;
}

函数Book和它的原型之间的关系就可以用下图描述。

Book函数可以是一个普通函数,也可以是一个构造函数。构造函数和普通函数的唯一区别是,通过new操作符来调用。

在这里,我们把它用作一个构造函数,并新建一个Book实例 book。实例book有个__protot__属性,也是指向原型。

let book = new Book("JavaScript高级程序设计","Nicholas");

我们会使用instanceof来检测一个实例属于哪个类,拿本例来说,就是这样:

book instanceof Book

这很简单啊,返回true嘛。为什么?事实上,它是做了一个这样的判断:

book.__proto__ === Book.prototype

判断一个实例和原型之间关系,除了以上方法外,还有另外两个方法:

//第二种
Object.getPrototypeOf(book) === Book.prototype   

//第三种
Book.prototype.isPrototypeOf(book)               

book-->Book.prototype这条原型链上,book继承了其所有父级的所有属性和方法。当然,这个例子很简单,因为book只有Book.prototype这一个父辈。下面我们看看Object.create()是怎样建立原型链的。

2、Object.create()建立的原型链

举个例子

const parent = {};
parent.a = "hello";
parent.b = "world";
const child = Object.create(parent);
parent.__proto__ === Object.prototype; //返回true
child.__proto__ === parent;            //返回true

parent instanceof Object; //返回true
child instanceof Object;  //返回true


console.log(child.a,child.b);//输出 "hello" "world"

上例建立的原型链用下图来描述。

之所以child instanceof Object返回true,是因为child沿着__proto__这条路线(黄颜色)往上搜索,Object沿着prototype这条路线(浅蓝色)往上搜索,它们最终指向了同一个引用,即Object.prototype。

child-->parent-->Object.prototype这条原型链上,child继承了其所有父级的所有属性和方法,所以,我们访问child.a会返回"hello",访问child.b会返回"world",当然也可以调用Object.prototype上的方法,比如child.toString()。

3、Object.create()工作原理

MDN提供的polyfill可以帮助我们更好地理解Object.create()的工作原理。

function inherit(parent){
	function F(){}
	F.prototype = parent;
	return new F();
}
const parent = inherit(null);
parent.a = "hello";
parent.b = "world";
const child = inherit(parent);

console.log(parent.__proto__ === Object.prototype,child.__proto__ === parent);
console.log(parent instanceof Object,child instanceof Object);
console.log(child.a,child.b);//输出 "hello" "world"

这段代码和第3节的代码产生的原型链是一样的。可能需要提醒的是,函数也是一个对象,Object.prototype是原型链上的最顶端

4、Object.create(null)

其实,我最想聊的是Object.create(null),哈哈哈。

const parent = Object.create(null);
parent.a = "hello";
parent.b = "world";
const child = Object.create(parent);

parent instanceof Object; //返回false
child instanceof Object;  //返回false

parent.__proto__;//undefined
child.__proto__;//undefined

console.log(Object.prototype.hasOwnProperty.call(child,"a"));  //输出false
console.log(Object.prototype.hasOwnProperty.call(child,"b"));  //输出false
console.log(child.a,child.b);//输出"hello" "world"

同样,给下原型链的图吧。

所以,根据前两节给出的instanceof的判断原理,很好理解为什么parent和child不属于Object了。

此时的parent和child 跟Object一点关系都没有。Object.create(null)突破了"Object.prototype是原型链上的最顶端"的限制

const parent=Object.create(null),parent继承自null,null不具有任何属性和方法,所以parent是一个空对象,即{}。

parent.a = "hello";parent.b="world";使得parent拥有了a和b这两个属性。

const child = Object.create(parent),child也是一个空对象,{},它本身是没有a属性和b属性的,这也是为什么Object.prototype.hasOwnProperty.call(child,a)会返回false。但child继承自parent,发现在自身上没有找到a和b时,会继续沿着原型链往上找,找到了parent,发现它有,于是就把parent.a和parent.b的值返回了,也就是"hello"和"world"。

4、Object.create()的应用

如果看过Vue源码,会发现Object.create()的使用频率是很高的,现在截一小段代码来窥探下Object.create是如何在Vue中发挥作用的。

v-model和v-show是大家常用的指令,platformDirectives是和这两个指令相关的全局变量,以下代码主要和v-model的解析有关。

var directive = {
	inserted:function inserted(el,binding,vnode,oldVnode){},
	componentUpdated:function componentUpdated(el,binding,vnode){}
}
var show = {
	bind:function bind(el,ref,vnode){},
	update:function update(el,ref,vnode){},
	unbind:function unbind(el,binding,vnode,oldVnode,isDestroy){}
}
var platformDirectives = {
	model:directive,
	show:show
}
var ASSET_TYPES = ['component','directive','filter'];

function Vue(options){
	this._init(options);
}
Vue.options = Object.create(null);
ASSET_TYPES.forEach(function(type){
	Vue.options[type+'s'] = Object.create(null);
});
extend(Vue.options.directives,platformDirectives);

Vue.prototype._init = function(options){
	var vm = this;
	vm.$options = mergeOptions(vm.constructor.options,options || {});
	return vm;
}
function mergeOptions(parent,child){
	var options = {};
	var key,parentVal,childVal;
	for(key in parent){
		parentVal = parent[key];
		var res = Object.create(parentVal||null);
		options[key] = res;
	}
	for(key in child){
		parentVal = parent[key];
		childVal = child[key];
		options[key] = childVal===undefined?parentVal:childVal;
	}
	return options;
}
function extend(to,_from){
	for(var key in _from){
		to[key] = _from[key];
	}
	return to;
}

const app = new Vue({
	el:"#app",
	data:{
		context:"hello world"
	}
});
const dirs = [{
	name:"model",
	rawName:"v-model",
	value:"hello world",
	expression:"context"

}]
function normalizeDirectives$1(dirs,vm){
	var res = Object.create(null);
	if(!dirs){
		return res;
	}
	var i,dir;
	for(i=0;i<dirs.length;i++){
		dir = dirs[i];
		res[dir.rawName] = dir;
		dir.def = resolveAsset(vm.$options,'directives',dir.name);
	}
	return res;
}
function resolveAsset(options,type,id){
	var assets = options[type];
	if(Object.prototype.hasOwnProperty.call(assets,id)){
		return assets[id];
	}
	var res = assets[id];
	return res;
}
const res = normalizeDirectives$1(dirs,app);

可以说是,用了一路的Object.create()。我们再从中挑出一撮代码,并代入参数值,得到下面一小部分。

第3行:app.$options['directives']本身是个空对象,{},不具备任何属性,但通过Object.create(),从原型链上继承了Vue.options['directives']上的所有属性。

第21行:使用Object.prototype.hasOwnProperty.call()判断app.$options['directives']自身是否具有model属性,有就返回自身的;

最后一行:自身如果没有model属性,就继续沿着原型链往上搜索,于是在Vue.options['directives']找到了,并返回。

function Vue(options){
	var vm = this;
	vm.$options['directives'] = Object.create(Vue.options['directives']);
	vm.$options['data'] = options['data'];
	vm.$options['el'] = options['el'];
	return vm;
}
Vue.options = Object.create(null);
Vue.options['directives'] = Object.create(null);
Vue.options['directives'] = platformDirectives;
var app = new Vue({
	el:"#app",
	data:{
		context:"hello world"
	}
});


var res;
var assets = app.$options['directives'];
if(Object.prototype.hasOwnProperty.call(assets,"model")){
	res = assets["model"];
}
res = assets["model"];

 

;