查看jQuery源码可以发现,jQuery中没有使用new操作符来创建新对象,而是采用调用jQuery原型中init()函数的方式返回一个新对象。
1.首先简单回忆下JavaScript中的原型。
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按照字面意思来理解,那么prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype 属性所在函数的指针。而通过这个构造函数,我们还可继续为原型对象添加其他属性和方法。
function A(){ } A.prototype.name="hello"; A.prototype.sayname=function(){ window.alert(this.name); }; var a=new A(); a.sayname(); //hello var b=new A(); b.sayname(); //hello
2.JavaScript中创建对象的几种方式。
1)工厂模式
function createBook(name,edition,author){ var o={}; o.name=name; o.edition=edition; o.author=author; o.sayName=function (){ console.log(this.name); }; return o; } var book_js=createBook("JavaScript","3","Nicholas"); book_js.sayName();
缺点:工厂模式虽然解决了创建多个相似对象的代码复用问题,但是却没有解决对象的识别问题。
2)构造函数模式
function Book(name,edition,author){ this.name=name; this.edition=edition; this.author=author; this.sayName=function (){ console.log(this.name); }; } var book_css=new Book("CSS","3","Charles"); book_css.sayName(); console.log(book_css instanceof Book); //true
缺点:每个方法都要在买个实例上创建一遍。
3)原型模式
//一种形式 function Book(){} Book.prototype.name="Python"; Book.prototype.edition="2"; Book.prototype.author="Magnus"; Book.prototype.sayName=function(){ console.log(this.name); }; var book_python=new Book(); book_python.sayName(); //另一种形式 function Book(){} Book.prototype={ constructor:Book, //会导致constructor变为可枚举属性,解决方案见下方注释 name:"Python", edition:"2", author:"Magnus", sayName:function(){ console.log(this.name); } }; //ECMAScript5 : Object.defineProperty(Book.prototype,"constructor",{enumerable:false, value:Book}); var book_python=new Book(); book_python.sayName();
缺点:所有实例在默认情况下都将取得相同的值,没有参数传递环节;某个实例对于构造函数原型中属性的修改会影响其他所有实例。
function Book(){} Book.prototype={ constructor:Book, //会导致constructor变为可枚举属性,解决方案见下方注释 name:"Python", edition:"2", author:["Magnus"], sayName:function(){ console.log(this.name); } }; //ECMAScript5 : Object.defineProperty(Book.prototype,"constructor",{enumerable:false, value:Book}); var book_python1=new Book(); var book_python2=new Book(); book_python1.author.push("lie"); console.log(book_python2.author);//["Magnus", "lie"]
4)组合使用构造函数模式和原型模式
function Book(name,edition,author){ this.name=name; this.edition=edition; this.author=[author]; } Book.prototype={ constructor:Book, sayName:function(){ console.log(this.name); }, sayAuthor:function(){ console.log(this.author); } }; var book_ajax=new Book("Ajax","1","Riordan"); var book_php=new Book("PHP","4","Luke"); book_ajax.sayAuthor();//["Riordan"] book_ajax.author.push("苏金国"); book_ajax.sayAuthor();//["Riordan", "苏金国"] book_php.sayAuthor();//["Luke"]
缺点:只要使用原型,就会使得在调用原型上方法时,在作用域链上多搜索一层。
基于以上分析,jQuery对象的构建如果在性能上考虑,所以就必须采用原型式的结构:
jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context ); } jQuery.fn = jQuery.prototype = { init:function(){ return this }, jquery: version, constructor: jQuery, ……………… } var a = $() ;
或者可以写出一个更接近jQuery源码的实现:
( function(window){ var ajQuery = function(selector) { //把原型上的init作为构造器 return new ajQuery.fn.init( selector ); }; ajQuery.fn = ajQuery.prototype = { name: 'aaron', init: function() { console.log(this); }, constructor: ajQuery }; window.ajQuery=ajQuery; }(window) ); console.log(typeof ajQuery);//function var aobj=ajQuery();//a…y.fn.a…y.init {} console.log(typeof aobj);//object
注意,源码中还传入了undefined,作用是为了防止undefined在全局中被修改和缩短作用域链的查询。