jQuery源码学习

论坛 期权论坛 脚本     
匿名网站用户   2020-12-21 09:50   14   0

jQuery使用上的几大特点

1、使用$()函数方式直接生成jQuery对象

2、链式调用(在函数的结尾返回this,this的值是根据函数运行的时候确定的)

无new函数创建对象:把jQuery.prototype.init.prototype = jQuery.prototype

jQuery.fn,它实际上是 prototype 的一个引用,指向 jQuery.prototype 。好处:书写简洁

var jQuery = function(){
  return new jQuery.fn.init();
}
jQuery.fn = jQuery.prototype = {
  constructor: jQuery,
  init: function(){
    this.jquery = 3.0;
    return this;
  },
  each: function(){
    console.log('each');
    return this;
  }
}
jQuery.fn.init.prototype = jQuery.fn;
jQuery().each().each();
// 'each'
// 'each'

1、$.globalEval( code )-----全局性的执行一段代码

var name = "全局变量";
$(function () { 
    function test(){
        var name = "局部变量";
        alert(name); // 局部变量
        eval( "alert(name);" ); // 局部变量
        $.globalEval( "alert(name);" );  // 全局变量
    }
    test();
})

大致思路:首先创建script元素;然后将传进来的参数赋给script.text属性;最后采用链式写法,将创建的script对象添加到head对象里面(这时候就执行JS代码),然后再将添加的script对象删除。

// 部分源码
globalEval: function(a) {
 p(a)
}

function p(a, b) {
 b = b || d;    //b没有传进来*(只传了一个参数),则b=document
 var c = b.createElement("script");
 c.text = a,
 b.head.appendChild(c).parentNode.removeChild(c)
}

2、$.extend()---用于将一个或多个对象的内容合并到目标对象(给jQuery扩展静态属性和方法)-----默认是浅拷贝,返回目标对象

1)扩展到对象层面--jQuery.extend(),扩展的是静态方法,可以直接使用$调用;

2)扩展到选择器函数对象层面----jQuery.fn.extend(),扩展的方法是实例的方法,由$('#id名')或者$('.类名')调用

具体作用:

1)将两个或更多对象的内容合并到第一个对象(如果只有一个参数对象,则将其扩展到jQuery对象上)---实现默认字段的覆盖

jQuery.extend( target [, object1 ] [, objectN ] )
function getOpt(target, obj1, obj2, obj3){
 $.extend(target, obj1, obj2, obj3);
 return target;
}
 
var _default = {
 name : 'wenzi',
 age : '25',
 sex : 'male'
}
var obj1 = {
 name : 'obj1'
}
var obj2 = {
 name : 'obj2',
 age : '36'
}
var obj3 = {
 age : '67',
 sex : {'error':'sorry, I dont\'t kown'}
}
getOpt(_default, obj1, obj2, obj3); // {name: "obj2", age: "67", sex: {error: "sorry, I dont't kown"}}

2)为jQuery或者jQuery原型扩展方法或属性----如果extend()函数只传一个对象参数,jQuery对象本身被默认为目标对象,浅拷贝

$.extend({
 _name : 'wenzi',
 _getName : function(){
    return this._name;
 }
})
 
$._name; // wenzi
$._getName(); // wenzi

3)深度拷贝和浅度拷贝----若第一个参数是boolean类型,且值是true(深拷贝)或者false(浅拷贝),把第二个参数作为目标参数进行合并

                jQuery.extend( [deep ], target, object1 [, objectN ] )
                //深复制
                var obj = {name:'wenzi', score:80};
                var obj1 = {score:{english:80, math:90}}
                $.extend(true, obj, obj1);
                obj.score.english = 10;
                console.log(obj.score.english); // 10
                console.log(obj1.score.english); // 80
 
                //浅复制
                var obj = {name:'wenzi', score:80};
                var obj1 = {score:{english:80, math:90}}
                $.extend(false, obj, obj1);
                obj.score.english = 10;
                console.log(obj.score.english); // undefined
                console.log(obj1.score.english); // 80

jQuery中的extend()实现原理

大致过程:对后一个参数进行循环,然后把后面参数上所有的字段都给了第一个字段,若第一个参数里有相同的字段,则进行覆盖操作,否则就添加一个新的字段

1)对第一个参数做判断,如果不是Boolean类型,且只有一个参数,那么就把jQuery作为target,然后把第一个参数上的字段都赋给target,最后返回target(浅拷贝);如果有多于一个参数,那么第一个参数就是target,然后把后面参数的字段都赋给target,最后返回target(浅拷贝)

2)第一个参数是boolean类型,表示深浅拷贝,true表示深拷贝,false表示浅拷贝。只有两个参数,那么就把jQuery作为target,把第二个参数的字段赋给target,然后返回target。多于两个参数,把第二个参数作为target,然后把后面的参数的字段赋给target,最后面返回target.

// 为与源码的下标对应上,我们把第一个参数称为`第0个参数`,依次类推
jQuery.extend = jQuery.fn.extend = function() {
 var options, 
   name, 
   src, 
   copy, 
   copyIsArray, 
   clone,
     target = arguments[0] || {}, // 默认第0个参数为目标参数
   i = 1, // i表示从第几个参数开始将目标参数与其进行合并,默认从第1个参数开始向第0个参数进行合并
   length = arguments.length,
  deep = false; // 默认为浅拷贝
  
 // 1、判断第0个参数的类型,若第0个参数是boolean类型,则获取其为true还是false
 // 同时将第1个参数作为目标参数,i从当前目标参数的下一个
 // Handle a deep copy situation
 if ( typeof target === "boolean" ) {
     deep = target;
 
   // Skip the boolean and the target
   // 如果第一个参数是Boolean类型
   target = arguments[ i ] || {};
  i++;
 }
 
 ///2、判断目标参数的类型,若目标参数既不是object类型,也不是function类型,则为目标参数重新赋值空对象
 // Handle case when target is a string or something (possible in deep copy)
 if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
   target = {};
 }
 
 ///3、若目标参数后面没有参数了,如$.extend({_name:'wenzi'}), $.extend(true, {_name:'wenzi'})
 // 则目标参数即为jQuery本身,而target表示的参数不再为目标参数
 // Extend jQuery itself if only one argument is passed
 if ( i === length ) {
  target = this;
  i--;
 }
 
 // 4、从第i个参数开始遍历
 for ( ; i < length; i++ ) {
  // 获取第i个参数,且该参数不为null和undefind,在js中null和undefined,如果不区分类型,是相等的,null==undefined为true,
  // 因此可以用null来同时过滤掉null和undefind
  // 比如$.extend(target, {}, null);中的第2个参数null是不参与合并的
  // Only deal with non-null/undefined values
  if ( (options = arguments[ i ]) != null ) {
   
   ///5、使用for~in获取该参数中所有可枚举的属性
   // Extend the base object
   for ( name in options ) {
    src = target[ name ]; // 目标参数中name字段的值
    copy = options[ name ]; // 当前参数中name字段的值,有可能是值,Object,Array
   
    ///6、若参数中属性的值就是目标参数,停止赋值,进行下一个字段的赋值
    // 这是为了防止无限的循环嵌套,我们把这个称为,在下面进行比较详细的讲解
    // Prevent never-ending loop
    if ( target === copy ) {
       continue;
    }
   
    ///7、若deep为true,且当前参数中name字段的值存在且为object类型或Array类型,则进行深度赋值
    // Recurse if we're merging plain objects or arrays
    // jQuery.isPlainObject()返回值为Boolean类型,如果指定的参数是纯粹的对象,则返回true,否则返回false
    if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
     // 若当前参数中name字段的值为Array类型
     // 判断目标参数中name字段的值是否存在,若存在则使用原来的,否则进行初始化
     if ( copyIsArray ) {
      //被复制的属性的值类型为Array
        copyIsArray = false;
        //判断该字段在target对象是否存在,存在且为Array,则直接使用原对象,否则创建空的Array
        clone = src && jQuery.isArray(src) ? src : [];
      
     }else{
      // 被复制的属性的值得类型为Object
        //判断该字段在target对象是否存在,若存在且为Object则直接使用,否则创建空的Object
        clone = src && jQuery.isPlainObject(src) ? src : {};
     }
   
     // 递归处理,此处为2.2
     // Never move original objects, clone them  
     target[ name ] = jQuery.extend( deep, clone, copy );
   
     // deep为false,则表示浅度拷贝,直接进行赋值
    // Don't bring in undefined values
    }else if ( copy !== undefined ) {   
     // 若copy是简单的类型且存在值,则直接进行赋值
        // 若原对象存在name属性,则直接覆盖掉;若不存在,则创建新的属性
        target[ name ] = copy;
    }
      }
   }
 }
  
 // 返回修改后的目标参数
 // Return the modified object
 return target;
};

3、jquery中type()-----$.type(任意类型对象)
作用---------确定JavaScript内置对象的类型,并返回小写形式的字符串类型名称

               //1、对于对象是undefined  null类型 ,如果是对象上没有的属性,则返回'undefined'    结果都为true
                console.log($.type( undefined ) === "undefined");
                console.log($.type() === "undefined");
                console.log($.type( window.notDefined ) === "undefined");
                console.log($.type( null ) === "null");

                // 2、js中内置的对象
                console.log( jQuery.type( true ) === "boolean" );
                console.log( jQuery.type( new Boolean() ) === "boolean" );
                console.log( jQuery.type( 3 ) === "number");
                console.log( jQuery.type( "test" ) === "string" );
                console.log( jQuery.type( function(){} ) === "function");
                console.log( jQuery.type( [] ) === "array" );
                console.log( jQuery.type( new Date() ) === "date");
                console.log( jQuery.type( new Error() ) === "error" );
                console.log( jQuery.type( Symbol() ) === "symbol" );
                console.log( jQuery.type( /test/ ) === "regexp");
                console.log( jQuery.type( {} ) === "object");
//因为 jQuery 用的是 toString 方法,所以需要有一个 class2type 的对象用来转换
var class2type = {
    "[object Boolean]": "boolean",
    "[object Number]": "number",
    "[object String]": "string",
    "[object Function]": "function",
    "[object Array]": "array",
    "[object Date]": "date",
    "[object RegExp]": "regexp",
    "[object Object]": "object",
    "[object Error]": "error",
    "[object Symbol]": "symbol",
    "[object Set]": "set",
    "[object Map]": "map"
}
var toString = Object.prototype.toString;

jQuery.type = function (obj) {
    if (obj == null) {
        return obj + "";
    }
    return 
      typeof obj === "object" || typeof obj === "function" ? 
        class2type[toString.call(obj)] || "object" : 
        typeof obj;
}

原理:对于null或undefined类型直接返回,如果是object或function类型则调用Object.prototype.toString.call()函数来判断参数的类型

4、jquery中的isPlainObject(object)----判断指定参数是否是一个纯粹的对象

//object ---任意类型
$.isPlainObject( object )

var getProto = Object.getPrototypeOf;     //获取父对象---原型对象
var hasOwn = class2type.hasOwnProperty;   //自身是否具有指定的属性,不包括原型上的属性,
var fnToString = hasOwn.toString;
var ObjectFunctionString = fnToString.call( Object );
jQuery.isPlainObject = function (obj) {
    var proto, Ctor;

    // 排除 underfined、null 和非 object 情况
    if (!obj || toString.call(obj) !== "[object Object]") {
        return false;
    }

    // 获取原型对象
    proto = getProto(obj); 

    // Objects with no prototype (e.g., `Object.create( null )`) are plain
    if (!proto) {
        return true;
    }

    // Objects with prototype are plain iff they were constructed by a global Object function
    Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
    return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
}

5、$.isArray = Array.isArray( obj )-----判断参数是否是数组

实现原理:Object.prototype.toString.call(obj) === '[object Array]'

if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}

6、$.fn.css()-----设置或返回被选元素的一个或多个样式属性

例子:
//1、返回指定的 CSS 属性的值
css("propertyname");
$("p").css("background-color");

// 设置 CSS 属性
//2、单个css属性
css("propertyname","value");
$("p").css("background-color","yellow");

//3、设置多个 CSS 属性
css({"propertyname":"value","propertyname":"value",...});
$("p").css({"background-color":"yellow","font-size":"200%"});
//css()只有一个参数,参数为字符串则通过getComputedStyle(obj)['attr']返回该属性的值;为
// 对象则再次调用该函数css
//若 css 有两个参数,则为元素的style属性赋值
css : function(attr,val){
    for(var i = 0; i < this.length; i++){
      if(val == undefined){
        if(Object.prototype.toString.call(attr) === '[object Object]'){
          for(var key in attr){
            this.css(key, attr[key]);
          }
        }else if(typeof attr === 'string'){
          return getComputedStyle(this[i])[attr];
        }
      }else{
        this[i].style[attr] = val;
      }
    }
}

7、jquery中的merge()----合并两个数组内容到第一个数组

// first---Array类型 第一个用于合并的数组,合并后将包含第二个数组的内容
// Array类型 第二个用于合并的数组,该数组不会被修改
$.merge( first, second )

var arr1 = [ 0, 1, 2 ];
var arr2 = [ 2, 3, 4 ];

$.merge( arr1, arr2);
console.log(arr1);  //[0, 1, 2, 2, 3, 4]
console.log(arr2);  //[2, 3, 4]

$.merge( $.merge([], arr1), arr2);  //这样就不会修改原来的数组对象了
源码:对第二个参数数组进行遍历,添加到第一个参数数组里面,并修改第一个参数数组的length属性
 merge: function( first, second ) {
        var len = +second.length,
            j = 0,
            i = first.length;

        for ( ; j < len; j++ ) {
            first[ i++ ] = second[ j ];
        }

        first.length = i;

        return first;
    } 

8、jquery中的isArrayLike(obj)----判断参数是否为类数组

function isArrayLike(obj) {

  // Support: real iOS 8.2 only (not reproducible in simulator)
  // `in` check used to prevent JIT error (gh-2145)
  // hasOwn isn't used here due to false negatives
  // regarding Nodelist length in IE
  var length = !!obj && "length" in obj && obj.length,
      type = jQuery.type(obj);

  if (type === "function" || jQuery.isWindow(obj)) {
      return false;
  }

  return type === "array" || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj;
}

9、jquery中的makeArray()----将一个类似数组的对象转换为真正的数组对象

$.makeArray( object )
$(function () { 
    var elems = document.getElementsByTagName("div"); // 返回一个节点列表
    var arr = jQuery.makeArray(elems);
    arr.reverse(); //对列表的元素使用一个数组方法
    $(arr).appendTo(document.body);
})
var push = [].push;
jQuery.makeArray = function (arr, results) {
  var ret = results || [];

  if (arr != null) {
    if (isArrayLike(Object(arr))) {
        jQuery.merge(ret, typeof arr === "string" ? [arr] : arr);
    } else {
        push.call(ret, arr);
    }
  }

  return ret;
}

8、jquery中的each()----$.each()和$(选择器).each----each的实现采用迭代器模式

1)$(选择器).each( callback(index,val)):为每个匹配元素规定运行的函数
$(selector).each(function(index,element))

//例子
$("button").click(function(){
  $("li").each(function(){
    alert($(this).text())
  });
});

2)$.each(arr | obj,callback(index,item)):----对数组或对象遍历

// 遍历数组
var arr = [{name:"limeng",email:"xfjylimeng"},{name:"hehe",email:"xfjylimeng"}];
$.each(arr,function(index,item){
 console.log("索引:" + index + "对应值为:" + item.name); //参数index为遍历索引值,item为当前的遍历对象,可用this指代
});


var arr1 = [ "one", "two", "three", "four", "five" ];
$.each(arr1, function(index,val){  //this指向当前的遍历对象
 console.log(val);   //one   two  three  four   five
});


var arr2 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
$.each(arr2, function(index, val){
 console.log(val[0]);    //1   4   7
});

// 遍历对象
var obj = { one:1, two:2, three:3, four:4, five:5 };
$.each(obj, function(key, val) {
 console.log(obj[key]);    //1   2  3  4  5
});

jQuery.each()和jQuery.fn.each()的源码是一样的

原理:通过w(a)函数判断参数是否为类数组,是则通过for循环对类数组中的每个元素调用一个callback,通过call改变callback的上下文环境为当前元素;否则为对象,则通过for...in对对象的每个元素调用一个callback,通过call改变上下文环境

//返回Boolean类型的值
//判断数组是否是类数组----具有length属性和可通过index属性访问成员
//通过type()函数返回参数的类型---Object.prototype.toString.call()
function w(a) {
 var b = !!a && "length" in a && a.length,
 // 返回参数的类型
 c = r.type(a);
 return "function" !== c && !r.isWindow(a) && ("array" === c || 0 === b || "number" == typeof b && b > 0 && b - 1 in a)
}




each: function(a, b) {
 //a---array || object
 //b----callback(index,val)
 var c, d = 0;
 //是类数组
 if (w(a)) {
  //----array
  for (c = a.length; d < c; d++){
   //b----callback
   if(b.call(a[d], d, a[d]) === !1){
    break;
   } 
  } 
 } else{
  //object
  for(d in a){
   if (b.call(a[d], d, a[d]) === !1) {
    break;
   }
  } 
 } 
 return a
}

9、jquery中的trim(str)----去除字符串首尾的空白字符

原理:利用String的replace()和正则表达式以及三目运算符

s = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
trim: function(a) {
 return null == a ? "": (a + "").replace(s, "");
}

10、jquery中的makeArray()-----将一个类数组对象转换为真正的数组对象

类数组对象----具有length属性且不为负值;可通过数字下标访问元素。没有数组原型对象上内置的方法。jquery对象($('p')),arguments,NodeList对象(document.getElementsByNodeName('p'))
返回值----数组对象

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1136255
帖子:227251
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP