深入学习jquery源码之创建科学、复用率高的对象
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,默认情况下prototype属性会默认获得一个constructor(构造函数)属性, 这个属性是一个指向prototype属性所在函数的指针
function Person(){
}
引用类型存在的问题
function Obj(){
this.a=[]; //实例变量
this.fn=function(){ //实例方法
}
}
var o1=new Obj();
o1.a.push(1);
o1.fn={};
console.log(o1.a); //[1]
console.log(typeof o1.fn); //object
var o2=new Obj();
console.log(o2.a); //[]
console.log(typeof o2.fn); //function
在o1中修改了a和fn,而在o2中没有改变,由于数组和函数都是对象,是引用类型, 这就说明o1中的属性和方法与o2中的属性与方法虽然同名但却不是一个引用,而是对Obj对象定义的属性和方法的一个复制。 这个对属性来说没有什么问题,但是对于方法来说问题就很大了,因为方法都是在做完全一样的功能,但是却又两份复制,如果一个函数对象有上千和实例方法, 那么它的每个实例都要保持一份上千个方法的复制,这显然是不科学的
function Person(name){
this.name=name;
}
Person.prototype.printName=function(){
alert(this.name);
}
var person1=new Person('Byron');
var person2=new Person('Frank');
Person的实例person1中包含了name属性,同时自动生成一个__proto__属性,该属性指向Person的prototype,可以访问到prototype内定义的printName方法,大概就是这个样子的
根据上图可以看出Person对象会自动获得prototyp属性,而prototype也是一个对象,会自动获得一个constructor属性,该属性正是指向Person对象。 当调用构造函数创建一个实例的时候,实例内部将包含一个内部指针(很多浏览器这个指针名字为__proto__)指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间, 而不是实例与构造函数之间
prototype内属性、方法是能够共享
function Person(name){
this.name=name;
}
Person.prototype.share=[];
Person.prototype.printName=function(){
alert(this.name);
}
var person1=new Person('Byron');
var person2=new Person('Frank');
person1.share.push(1);
person2.share.push(2);
console.log(person2.share); //[1,2]
属性方法覆盖
实际上当代码读取某个对象的某个属性的时候,都会执行一遍搜索,目标是具有给定名字的属性,搜索首先从对象实例开始,如果在实例中找到该属性则返回, 如果没有则查找prototype,如果还是没有找到则继续递归prototype的prototype对象,直到找到为止,如果递归到object仍然没有则返回错误。 同样道理如果在实例中定义如prototype同名的属性或函数,则会覆盖prototype的属性或函数。
function Person(name){
this.name=name;
}
Person.prototype.share=[];
var person=new Person('Byron');
person.share=0;
console.log(person.share); //0而不是prototype中的[]
构建一个复用率高的对象
如果希望实例对象的属性或函数则定义到prototype中, 如果希望每个实例单独拥有的属性或方法则定义到this中,可以通过构造函数传递实例化参数。
function Person(name){
this.name=name;
}
Person.prototype.share=[];
Person.prototype.printName=function(){
alert(this.name);
}
constructor
在 Javascript 语言中,constructor 属性是专门为 function 而设计的,它存在于每一个 function 的prototype 属性中。这个 constructor 保存了指向 function 的一个引用。
在定义一个函数时,JavaScript 内部会执行如下几个动作:
1.为该函数添加一个原形(即 prototype)属性
2. 为 prototype 对象额外添加一个 constructor 属性,并且该属性保存指向函数F 的一个引用
这样当我们把函数 F 作为自定义构造函数来创建对象的时候,对象实例内部会自动保存一个指向其构造函数(这里就是我们的自定义构造函数 F)的 prototype 对象的一个属性proto,
所以我们在每一个对象实例中就可以访问构造函数的 prototype 所有拥有的全部属性和方法,就好像它们是实例自己的一样。当然该实例也有一个 constructor属性了(从 prototype 那里获得的),每一个对象实例都可以通过 constrcutor 对象访问它的构造函数。
对象类型判断
var f = new F();
if(f.constructor === F) {
// do sth with F
}
其实 constructor 的出现原本就是用来进行对象类型判断的,但是 constructor 属性易变,不可信赖。我们有一种更加安全可靠的判定方法:instanceof 操作符。
if(f instanceof F) {
// do sth with F
}
原型链继承
一个子类对象可以获得其父类的所有属性和方法,称之为继承。
由于 constructor 存在于 prototype 对象上,因此我们可以结合 constructor 沿着原型链找到最原始的构造函数,如下面代码:
function Base() {}
// Sub1 inherited from Base through prototype chain
function Sub1(){}
Sub1.prototype = new Base();
Sub1.prototype.constructor = Sub1;
Sub1.superclass = Base.prototype;
// Sub2 inherited from Sub1 through prototype chain
function Sub2(){}
Sub2.prototype = new Sub1();
Sub2.prototype.constructor = Sub2;
Sub2.superclass = Sub1.prototype;
// Test prototype chain
alert(Sub2.prototype.constructor);// function Sub2(){}
alert(Sub2.superclass.constructor);// function Sub1(){}
alert(Sub2.superclass.constructor.superclass.constructor);// function Base(){}
constructor 易变,那是因为函数的 prototype 属性容易被更改,我们用时下很流行的编码方式来说明问题
function F() {}
F.prototype = {
_name: 'Eric',
getName: function() {
return this._name;
}
};
初看这种方式并无问题,但是你会发现下面的代码失效了:
var f = new F();
alert(f.constructor === F); // output false
F 不是实例对象 f 的构造函数了吗?当然是!只不过构造函数 F 的原型被开发者重写了,这种方式将原有的 prototype 对象用一个对象的字面量{}来代替。而新建的对象{}只是 Object 的一个实例,系统(或者说浏览器)在解析的时候并不会在{}上自动添加一个 constructor 属性,因为这是 function 创建时的专属操作,仅当你声明函数的时候解析器才会做此动作。然而你会发现 constructor 并不是不存在的。
既然存在,那这个 constructor 是从哪儿冒出来的呢?我们要回头分析这个对象字面量 {}。因为{}是创建对象的一种简写,所以{}相当于是 new Object()。那既然{}是 Object 的实例,自然而然他获得一个指向构造函数 Object()的 prototype 属性的一个引用proto,又因为 Object.prototype 上有一个指向 Object 本身的 constructor属性。所以可以看出这个constructor其实就是Object.prototype的constructo
一个解决办法就是手动恢复他的 constructor,下面代码非常好地解决了这个问题:
function F() {}
F.prototype = {
constructor: F, /* reset constructor */
_name: 'Eric',
getName: function() {
return this._name;
}
};
var f = new F();
alert(f.constructor === F); // output true this time ^^
构造函数上怎么还有一个 constructor ?它又是哪儿来的?细心的会发现,像 JavaScript 内建的构造函数,如 Array, RegExp, String,Number, Object, Function 等等居然自己也有一个 constructor:
经过测试发现,此物非彼物它和 prototype 上 constructor 不是同一个对象,他们是共存的:
alert(typeof Array.constructor != 'undefined');// output true
alert(typeof Array.prototype.constructor === Array); // output true
在JavaScript中,每个具有原型的对象都会自动获得constructor属性。除了arguments、Enumerator、Error、Global、Math、RegExp、Regular Expression等一些特殊对象之外,其他所有的JavaScript内置对象都具备constructor属性。例如:Array、Boolean、Date、Function、Number、Object、String等。所有主流浏览器均支持该属性
不过这件事情也是好理解的,因为 构造函数也是函数。是函数说明它就是 Function 构造函数的实例对象,自然他内部也有一个指向 Function.prototype 的内部引用proto啦。因此我们很容易得出结论,这个 constructor(构造函数上的constructor 不是 prototype 上的)其实就是 Function 构造函数的引用:
alert(Array.constructor === Function);// output true
alert(Function.constructor === Function); // output true
构造函数(Constructor)在对象创建或者实例化时候被调用的方法。通常使用该方法来初始化数据成员和所需资源。构造器Constructor在js不能被继承,因此不能重写Overriding,但可以被重载Overloading
在一个构造方法中可以使用super
关键字来调用一个父类的构造方法。
如果没有显式指定构造方法,则会添加默认的 constructor 方法。
如果不指定一个构造函数(constructor)方法, 则使用一个默认的构造函数(constructor)。
class Polygon {
// ..and an (optional) custom class constructor. If one is
// not supplied, a default constructor is used instead:
// constructor() { }
constructor(height, width) {
this.name = 'Polygon';
this.height = height;
this.width = width;
}
// Simple class instance methods using short-hand method
// declaration
sayName() {
ChromeSamples.log('Hi, I am a ', this.name + '.');
}
sayHistory() {
ChromeSamples.log('"Polygon" is derived from the Greek polus (many) ' +
'and gonia (angle).');
}
// We will look at static and subclassed methods shortly
}
// Classes are used just like ES5 constructor functions:
let p = new Polygon(300, 400);
p.sayName();
ChromeSamples.log('The width of this polygon is ' + p.width);
调用
class Square extends Polygon {
constructor(length) {
// 在这里, 它调用了父类的构造函数, 并将 lengths 提供给 Polygon 的"width"和"height"
super(length, length);
// 注意: 在派生类中, 必须先调用 super() 才能使用 "this"。
// 忽略这个,将会导致一个引用错误。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
set area(value) {
// 注意:不可使用 this.area = value
// 否则会导致循环call setter方法导致爆栈
this._area = value;
}
}
let s = new Square(5);
s.sayName();
ChromeSamples.log('The area of this square is ' + s.area);
js对象的constructor属性返回创建该对象的函数的引用。
// 字符串:String()
var str = "张三";
document.writeln(str.constructor); // function String() { [native code] }
document.writeln(str.constructor === String); // true
// 数组:Array()
var arr = [1, 2, 3];
document.writeln(arr.constructor); // function Array() { [native code] }
document.writeln(arr.constructor === Array); // true
// 数字:Number()
var num = 5;
document.writeln(num.constructor); // function Number() { [native code] }
document.writeln(num.constructor === Number); // true
// 自定义对象:Person()
function Person(){
this.name = "CodePlayer";
}
var p = new Person();
document.writeln(p.constructor); // function Person(){ this.name = "CodePlayer"; }
document.writeln(p.constructor === Person); // true
// JSON对象:Object()
var o = { "name" : "张三"};
document.writeln(o.constructor); // function Object() { [native code] }
document.writeln(o.constructor === Object); // true
// 自定义函数:Function()
function foo(){
alert("CodePlayer");
}
document.writeln(foo.constructor); // function Function() { [native code] }
document.writeln(foo.constructor === Function); // true
// 函数的原型:bar()
function bar(){
alert("CodePlayer");
}
document.writeln(bar.prototype.constructor); // function bar(){ alert("CodePlayer"); }
document.writeln(bar.prototype.constructor === bar); // true