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"];