1.1共享原型设计
1.2熟知JS的命名规则
变量的命名规则:
- 标识符只能由字母,数字,下划线,和$组成;
- 数字不可以作为标识符的首字母;
对象属性的命名规则:
- 通过[]操作符为对象添加属性时,属性名称可以是任何字符串(包括只包含空格的字符串和空字符串);
- 通过.操作符为对象添加属性时,属性名称必须是合法的标识符名称;
- 如果属性名包含非法的标识字符,则只能采用obj[“propertyName”]的形式;
- 如果属性名是合法的标识符,读取时即可以采用obj.propertyName,也可以采用obj[“propertyName”]的形式;
纯数字属性名的特殊性:
- 纯数字属性名的使用比较特殊,可以通过对象字面量和obj[“propertyName”]的形式为对象添加纯数字属性,解释器会自动将数字转换为数字字符串;
布尔属性名的特殊性:
- 布尔属性名和纯数字属性名有着相似的行为,即解释器会自动将布尔转换为布尔字符串;
采用[ ]操作符读取对象属性时,JS解释器执行的动作:
- 采用obj[“propertyName”]的形式读取或创建对象属性时,解释器首先会检查propertyName是值类型字面量还是用户定义的变量,如果propertyName是值类型的字面量则解释器自动将其转换为字符串后再读取或创建属性
- 如果propertyName是变量名称(或者是表达式)则解释器会读取变量内容(或对表达式求值)
- 如果变量的值是字符串则直接读取属性
- 如果是其他类型的数据则转换为字符串后再读取属性
JS的类数组概念
- 类数组实质为对象但有一些数组的方法,用起来像数组,因为其本质为对象,也有对象的用法故称为"类数组"
- 在类数组中的属性要为索引(数字)属性,必须有length属性,最好加上push方法
1.3jQuery实现的无new构造$()
一般地我们会误以为jQuery构造$这个对象会这样地实现
(function(root){
// 步骤1.闭包中实现一个jQuery实例对象
var jQuery = function (){
// 步骤2.创建jQuery实例返回
return new jQuery()
}
// 步骤3.定义jQuery原型属性和方法
jQuery.prototype = {
......
}
// 步骤4.创建外部上下文$对象和jQuery对象都指向内部创建的jQuery对象
root.$ = root.jQuery = jQuery
})(this)
上述代码中咋眼一看就能实现外部无new构造实现$(),代码也好像没有什么问题然而在获取jQuery实例中我们知道步骤2的new jQuery()实现构造jQuery实例会导致无限次new的死循环错误,故而不能这样简单地进行构造闭包内部的jQuery实例
方法改进:采用jQuery原型的共享原型类进行构造jQuery实例
(function(root){
// 步骤1.闭包中实现一个jQuery实例对象引用
var jQuery
// 步骤2.创建jQuery属性fn指向jQuery的原型
jQuery.fn = jQuery.prototype = {
// 步骤3.在jQuery原型上创建init类
init: function () {},
......
}
// 步骤4.实现共享原型(关键步骤)
jQuery.fn.init.prototype = jQuery.fn
// 步骤5.通过原型创建jQuery来实现无new构造,解决new的死循环构造
jQuery = function (){
return new jQuery.prototype.init()
}
// 步骤6.创建外部上下文$对象和jQuery对象都指向内部创建的jQuery对象
root.$ = root.jQuery = jQuery
})(this)
关键步骤jQuery.fn.init.prototype = jQuery.fn使得jQuery原型与init的原型实现共享
1.4实现jQuery的extend函数
extend函数作用: 在jQuery中extend函数用于将一个或多个对象的内容合并到目标对象
(function(root){
var jQuery = function(){
// 通过原型创建jQuery来实现无new构造,解决new的死循环构造
return new jQuery.prototype.init()
}
// jQuery的属性fn指向jQuery的原型
jQuery.fn = jQuery.prototype = {
init: function(){},
}
// extend扩展函数的实现(使得外部不管是$.fn.extend或者$.extend的形式调用最终都指向同一个匿名函数)
jQuery.fn.extend = jQuery.extend = function(){
// 判断参数来进行确定是为实例对象扩展还是自身属性的扩展
var target = arguments[0] || {} // 如果第一个参数对象为空则创建一个空对象赋值
var length = arguments.length
var i = 1 // 操作参数的位置(如果传入的第一个参数是boolean则从第二个参数开始操作扩展)
var deep = false // 作深一层的考量是否需要深度拷贝
var option,name,copy,src,copyIsArray,clone
if(typeof target === "boolean"){
deep = target
target = arguments[1]
i = 2
}
if(typeof target !== "object"){
target = {}
}
// 参数的个数只为一个则是对jQuery本身扩展
if(length === i){
target = this;
i--; // 把i变成0使得for循环能够进行浅拷贝的执行
}
// 浅拷贝: 遍历第二个及其后面的参数对象 深拷贝: 遍历第三个及其后面的参数对象
for(;i < length;i++){
if(arguments[i]){
if((option = arguments[i]) != null){
for(name in option){
// 实现深度拷贝和浅度拷贝的关键点
copy = option[name]
src = target[name]
// 若当前模式为深拷贝,copy的值则必须为对象或数组(两者之一)
if(deep && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) )){
if(copyIsArray){ // copy是一个数组
copyIsArray = false
clone = src && jQuery.isArray(src) ? src : []; // 如果当前的target不是数组则将clone设置为数组
} else { // 否则就是一个对象
clone = src && jQuery.isPlainObject(src) ? src : {}; // 如果当前的target不是对象则将clone设置为对象
}
target[name] = jQuery.extend(deep,clone,copy) // 递归完成深拷贝
}else if(copy != undefined){
target[name] = copy
}
}
}
}
}
return target
}
//实现共享原型
jQuery.fn.init.prototype = jQuery.fn
// 为当前的闭包中的jQuery实例扩展一些类型检测的方法(此时执行的是浅度拷贝的代码故深度拷贝的代码存在不会影响extend的执行)
jQuery.extend({
// 判断是否是对象类型
isPlainObject: function(obj){
return toString.call(obj) === "[object Object]"
},
// 判断是否是数组类型
isArray: function(obj){
return toString.call(obj) === "[object Array]"
}
})
root.$ = root.jQuery = jQuery;
})(this)// this指向当前的window对象
关键点在于深度和浅度拷贝,如果是浅度拷贝对象或数组内容只会被覆盖,如果是深度拷贝则会追加对象或数组的内容
下一篇JQuery设计模式之源码分析(二)