记录下JS中函数的对象特质、方法特质、构造函数特质,以及一些觉得必要的细节。
知识前提:对prototype和__proto__有基本的认识。
测试环境:chrome。
函数是对象是方法也是构造函数
var func = function(a) {
return a;
}
func.var_a = 1;
console.log(func.var_a);//1
console.log(func(2));//2
JS中一切皆对象,函数可以像普通对象一样被添加属性。
同时,不影响其方法特质。
var func = function(a) {
return a;
}
console.log(typeof func(2));//number
console.log(typeof new func(2));//object
第一行输出体现的是函数的方法特质,第二行就是构造函数的特质了。
前者类似调用了一个方法,后者是构造出了一个新的对象。
所以,函数它就在那里,看你如何用它
对象的成员访问
用函数构造了一个对象,然后怎么访问对象中的成员呢。
var func = function(a) {
this.inner_a = 1;
return a;
}
func.prototype.pro_a = 1;
func.var_a = 1;
var obj_a = new func();
var obj_b = new func();
console.log(obj_a.var_a);//undefined
obj_a.inner_a = 2;
console.log(obj_a.inner_a);//2
console.log(obj_b.inner_a);//1
var_a是func的成员,而obj_a和obj_b是根据func构造而来的对象,自然无法访问func的成员。
inner_a是obj_a和obj_b的成员,并且两者的inner_a不是同一个,因此改变a的自然不影响b的。
obj_a.pro_a = 2;
console.log(obj_a.pro_a);//2
console.log(obj_b.pro_a);//1
obj_a和obj_b本身都没有pro_a,然后给obj_a添加pro_a,再执行输出。
a输出的是自身的pro_a,b输出的是prototype的pro_a。
func.prototype.pro_a = 2;
console.log(obj_a.pro_a);//2
console.log(obj_b.pro_a);//2
修改obj_a和obj_b构造函数func的prototype中的成员后的输出。
所以,js中对对象的成员的遍历可见一斑。这应该是所谓原型链的范畴吧。
详解构造函数
var func = function(a) {
return a;
}
var func_b = function() {
}
console.log(new func().__proto__ == func.prototype);//true
上述代码表明了,实例的__proto__确实是指向构造函数的prototype。
JS遍历对象成员的时候依据的应该就是对__proto__的逐级遍历。
var func = function(a) {
return new func_b();
}
var func_b = function() {
}
console.log(new func().__proto__ == func.prototype);//false
console.log(func().__proto__ == func.prototype);//false
console.log(new func().__proto__ == func_b.prototype);//true
console.log(func().__proto__ == func_b.prototype);//true
如果在func中返回func_b的实例。
调用func()很好理解,会生成一个func_b的实例,console也证明这一点。
如果生成func的实例,则生成后的实例其实就是func_b的实例。
也就是说,此处func()和new func()效果一样。
jQuery的构造函数就是这样的。
来点折腾-1
var func = function(a) {
return func_b();
}
var func_b = function() {
}
console.log(new func().__proto__== func.prototype);//true
console.log(func().__proto__ == func.prototype);//抛异常
console.log(new func().__proto__ == func_b.prototype);
console.log(func().__proto__ == func_b.prototype);
如果在func中返回func_b的调用。
由于func_b()不返回任何东西,那new func()生成的就是func的实例,自然第一条输出是true的。
调用func()就和调用func_b效果一样,不返回任何东西,没有__proto__属性,自然抛异常。
来点折腾-2
var func = function(a) {
return func_b;
}
var func_b = function() {
}
console.log(new func().__proto__== func.prototype);//false
console.log(func().__proto__ == func.prototype);//false
console.log(new func().__proto__ == func_b.prototype);//false
console.log(func().__proto__ == func_b.prototype);//false
console.log(new func() == func());//true
console.log(new (new func())().__proto__== func_b.prototype);//true
console.log(new (func())().__proto__== func_b.prototype);//true
如果在func中返回func_b。
那么,func()和new func()其实返回的都是对象func_b。
因此,new (func())()和new (new func())()相当于new func_b()。最后两行console输出也证明了这点。
小结
1,函数具备对象特质。
关于对象成员遍历会涉及原型链(proto在其中扮演关键角色),简单点说就是向上逐级遍历,类似java之类的语言中,子类的成员如果不存在会向父类遍历。
2,函数同时具备方法特质和构造函数特质。
当构造函数中返回了另外一个构造函数,或者返回一个新建的对象,则构造函数返回的不再是构造函数本身构造的实例。此处用instanceof运算符能更加直白的证明。因此,函数构造是有嵌套概念在的,jQuery的构造就利用了此点。
模拟下jQuery的构造
var func = (function(){
var func = function() {
return new func_a();
}
func.prototype.a = 2;
var func_a = function() {
}
func_a.prototype = func.prototype;
return func;
})();
console.log(func().a);//2
自调用匿名函数其实返回的是func_a的实例。
为了能访问func的prototype中的内容,重置了func_a的prototype。