一、jQuery无new构建实例
1、$就是jQuery的别称
可以在$和jQuery对象挂在在window中,实现全局引用。
给Windows对象扩展一个$的属性,让它拿到jQuery构造函数的引用
可以用$访问到jQuery的构造函数
// jQuery.js
(function(root) {var jQuery = function(){}
root.$ = root.jQuery = jQuery;
})(this)
2、$()就是在创建jQuery的的实例对象
调用$()方法的目的是创建一个实例,构造函数直接调用是当做一个普通的函数来处理的
// jQuery.js
(function(root) {var jQuery = function() {}
})(this)
具体代码:
// jQuery.js
(function(root) {var jQuery = function() {console.log('生成一个实例对象');};root.$ = root.jQuery = jQuery;
})(this)
3、什么是构造函数
①构造函数也是一个普通函数,创建方式和普通函数一样,但习惯上首字母大写(小写也是可以的),用new关键字调用
②和普通函数的区别:
1)调用方式不一样
普通函数调用:直接调用,如,person()
构造函数调用:使用new关键字调用,如,new Person()
2)作用不同
构造函数用来新建实例对象
③构造函数的函数名和类名相同:Person()这个构造函数,Person既是函数名,也是这个对象的类名
④内部用this来构造属性和方法
function Person(name, gender, age) {this.name = name;this.gender = gender;this.age = age;this.sayHi=function(){alert("Hi")}
}
当创建上面的函数以后, 我们就可以通过 new 关键字调用,也就是通过构造函数来创建对象了
var p1 = new Person('张三', '男', 14); // 创建一个新的内存 #f1
var p2 = new Person('李四', '女', 28); // 创建一个新的内存 #f2
⑤构造函数的执行流程
A、立刻在堆内存中创建一个新的对象
B、将新建的对象设置为函数中的this
C、逐个执行函数中的代码
D、将新建的对象作为返回值
其他说法:
1、当以new关键字调用时,会创建一个新的内存空间 #f1,并标记为Person的实例
2、函数体内部的this指向该内存空间 #f1
以上两步:每当创建一个实例的时候,就会创建一个新的内存空间(#f1,#f2),创建#f1的时候,函数体内部的this指向#f1,创建#f2的时候,函数体内部的this指向#f2
3、执行函数体内的代码
给this添加属性,就相当于给实例添加属性
4、默认返回this
由于函数体内部的this指向新创建的内存空间(默认返回 this ,就相当于默认返回了该内存空间,也就是上图中的 #f1),而该内存空间(#f1)又被变量p1所接受。也就是说 p1 这个变量,保存的内存地址就是 #f1,同时被标记为 Person 的实例。
⑥普通函数:因为没有返回值,所以为undefined
function person(){}
var per = person()
console.log(per) // undefined
⑦构造函数
1)默认返回this
function Person1(){this.name = 'zhangsan';
}
var p1 = new Person1()
console.log(p1) // Person1 {name: "zhangsan"}
console.log(p1.name) // zhangsan
首先,当用 new 关键字调用时,产生一个新的内存空间 #f11,并标记为 Person1 的实例;
接着,函数体内部的 this 指向该内存空间 #f11;
执行函数体内部的代码;
由于函数体内部的this 指向该内存空间,而该内存空间又被变量 p1 所接收,
所以 p1 中就会有一个 name 属性,属性值为 ‘zhangsan’。
2)手动添加一个基本数据类型的返回值,最终还是返回 this
function Person2() {this.age = 28;return 50;
}
var p2 = new Person2();
console.log(p2) // Person2 {age: 28}
console.log(p2.age); // 28
如果上面是一个普通函数的调用,那么返回值就是 50
- 手动添加一个复杂数据类型(对象)的返回值,最终返回该对象
例一:
function Person3() {this.height = '180';return ['a', 'b', 'c'];
}var p3 = new Person3();
console.log(p3.height); // undefined
console.log(p3.length); // 3
console.log(p3[0]); // 'a'例二:
function Person4() {this.gender = '男';return { gender: '中性' };
}var p4 = new Person4();
console.log(p4.gender); // '中性'
⑧不用new关键字,直接运行构造函数,是否会出错?如果不会出错,那么,用new和不用new调用构造函数,有什么区别?
1)用new调用构造函数,函数内部会发生如下变化:
创建一个this变量,该变量指向一个空对象。并且该对象继承函数的原型;
属性和方法被加入到this引用的对象中;
隐式返回this对象(如果没有显性返回其他对象)
用伪程序来展示上述变化:
function Person(name){// 创建this变量,指向空对象var this = {}; // 属性和方法被加入到this引用的对象中this.name = name;this.say = function(){return "I am " + this.name;}// 返回this对象return this;
}
var person1 = new Person('nicole');
person1.say(); // "I am nicole"
可以看出,用new调用构造函数,最大特点为,this对象指向构造函数生成的对象,
所以,person1.say()会返回字符串: “I am nicole”。
注意:如果指定了返回对象,那么,this对象可能被丢失。
function Person(name){this.name = name;this.say = function(){return "I am " + this.name;}var that = {};that.name = "It is that!";return that;
}var person1 = new Person('nicole');
person1.name; // "It is that!"
2)直接调用函数
如果直接调用函数,this对象指向window,并且,不会默认返回任何对象(除非显性声明返回值)。
var person1 = Person('nicole');
person1; // undefined
window.name; // nicole
二、共享原型设计
按照正常思维,函数里面直接return一个new jQuery即可:
// jQuery.js
(function(root) {var jQuery = function() {return new jQuery();};root.$ = root.jQuery = jQuery;
})(this)
通过new的方式去创建jQuery的实例,实际上会做两步操作:
1、创建一个object对象
2、调用本身的构造函数
这样会引发死循环的问题,因为该函数在不断地创建一个新对象,不断的调用构造函数
那么jQuery是如何设计的呢?我们来看下jquery的共享原型设计图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vlwgJ7K6-1577168361128)(https://cdn.nlark.com/yuque/0/2019/png/467105/1567079008264-e9368f7c-41ec-4b66-bbed-a5ad0e90a2ba.png#align=left&display=inline&height=1328&name=jquery%E5%85%B1%E4%BA%AB%E5%8E%9F%E5%9E%8B%E8%AE%BE%E8%AE%A1%E5%9B%BE.png&originHeight=1328&originWidth=2376&size=505896&status=done&width=2376)]
从图中可以看出,当我们调用$的时候,实际上是在调用jQuery原型上的init方法,把init方法当成一个构造函数,然后返回它的实例对象。这样会产生一个问题,就是我们实际是要创建jQuery对象,并访问jquery原型上扩展的属性和方法。如何解决这个问题?jquery用了一个共享原型的设计,那就是将jQuery原型上的init构造函数和jQuery本身共享一个原型对象。
具体代码如下:
// jQuery.js
(function(root) {var jQuery = function() {// 如果想直接调用$()方法,我们尝试直接return// 但这种写法是错误的,如果直接return本身,会造成死循环// return new jQuery();return new jQuery.prototype.init;};给jQuery的原型扩展了一个init的方法jQuery.prototype = {init: function() {}}// 共享原型对象找到jQuery原型对象上的init方法,把它当做一个构造函数,设置它的原型对象指向jQuery的原型对象,达到共享原型的目的jQuery.prototype.init.prototype = jQuery.prototype;root.$ = root.jQuery = jQuery;
})(this)
这样我们打印console.log($()),就可以看到jquery原型对象的init方法了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SWBArZFf-1577168361138)(https://cdn.nlark.com/yuque/0/2019/png/467105/1567079723886-e9ea1980-6a09-42d3-b1d5-515a1262d28b.png#align=left&display=inline&height=201&name=image.png&originHeight=402&originWidth=1028&size=58076&status=done&width=514)]
这时候的 ( ) 实 际 指 向 的 是 j Q u e r y 原 型 上 的 i n i t 方 法 , 而 i n i t 的 原 型 和 j Q u e r y 本 身 的 原 型 共 享 一 个 原 型 对 象 , 这 样 往 j Q u e r y 上 扩 展 的 方 法 就 可 以 通 过 ()实际指向的是jQuery原型上的init方法,而init的原型和jQuery本身的原型共享一个原型对象,这样往jQuery上扩展的方法就可以通过 ()实际指向的是jQuery原型上的init方法,而init的原型和jQuery本身的原型共享一个原型对象,这样往jQuery上扩展的方法就可以通过()获取。
例如我们在jQuery的原型上扩展一个css的方法:
// jQuery.js
jQuery.prototype = {init: function() {},css: function() {}
}
console.log($())就可以看到css这个方法了

三、extend源码解析
jQuery的核心函数extend,用于对对象进行扩展,一般有三种方式:
1、对任意对象进行扩展,参数必须是两个及两个以上
第一个对象没有事扩展,有,是重新赋值
// demo.html
$.extend({}, { name: 'arbor'});
2、对jQuery本身扩展属性或者方法
// demo.html
$.extend({work: function() {}
})
jQuery.work();
3、对jQuery实例对象扩展属性或方法
// demo.html
$.fn.extend({sex: '男'
})
$().sex;
这里的$.fn_指的是jQuery的prototype原型对象_
那如何实现呢?
很简单,就是给jQuery扩展一个fn的方法,去等于jQuery的原型对象
// jQuery.js
jQuery.fn = jQuery.prototype = {init: function() {},css: function() {}
}
// 当然,共享原型对象也得写成:
// 共享原型对象
jQuery.fn.init.prototype = jQuery.fn;
下面,正题来了,核心函数extend是如何实现的呢?
首先需要保证 . f n 和 .fn和 .fn和.都可以调用到,其次_在jQuery的原型对象和jQuery对象上新建了同一个extend的函数_
// jQuery.js
jQuery.fn.extend = jQuery.extend = function() {}
如何判断是给什么进行扩展属性和方法呢?
通过参数来判断
A、给任意对象扩展,参数必须是两个及两个以上
B、给jQuery本身或者jQuery实例对象扩展,只需要传递一个参数
不管是哪种,第一个参数必须是object
**
其实,我们分析extend函数的调用方式,无外乎两种情况,一种是给任意对象扩展,从第二个参数开始作为对第一个参数的补充,一种是直接扩展,既然这样,我们就可以对第一个参数做文章了:
1、如果第一个参数是个对象,那么我们就可以把从第二个开始的参数进行遍历,然后填充到第一个参数中。这里可以用到arguments;
2、如果第一个参数不是对象,那么就简单了,直接把参数扩展到_jQuery对象或者jQuery的实例对象上即可,因为参数只有一个。这里要特别注意的是,this的指向问题,this是指向jQuery对象本身?还是jQuery的实例对象上?什么意思呢?当你往jquery对象上扩展一个方法或者属性时,这时候传参只有一个,那是不是要告诉程序,往哪里扩展?是往jQuery上扩展呢?还是往jQuery实例的原型对象扩展呢?详见代码18-24行。
既然如此,我们就来看看代码是如何实现的。
// 浅拷贝
jQuery.fn.extend = jQuery.extend = function() {// 定义一个变量target,用于存储传入的第一个值,如果值为空,那默认为空对象的引用/* * max* 通过arguments获取用户调用时传递的参数,获取第一个参数,如果没有传就创建一个对象并赋值给一个变量* 此时通过arguments[0]获取用户传递过来的第一个参数,但是并没有100%保证这个参数一定是一个object* 就要进行数据类型的判断:见* */var target = arguments[0] || {} // 定义一个变量len,用于存储传参的长度var len = arguments.length;// 定义一个遍历i,var i = 1;// option 第二个参数后面的参数遍历过程中单个的存储var option;// 判断参数一的类型,如果不是object就创建一个对象并赋值给它if (target !== 'object') {target = {};}// 如果参数只有一个,那么就是对jQuery对象或者jQuery的实例对象扩展// 例如: 1.jQuery对象的扩展 $.extend({ work: function() {}})// 2.jQuery的实例对象扩展 $.fn.extend({ sex: '男' })if (length === i) {// 这里的this等于函数调用的对象本身,即jQuery对象或者jQuery的实例对象target = this;}// 如果参数不止一个,给任意对象进行扩展的逻辑,例如$.extend({}, {name: 'arbor'});// 也就是浅拷贝for(; i < len; i++) {if ((option = arguments[i]) !== null) {for (key in option) {// target指的是第一个参数对象,进行扩展target[key] = option[key];}}}return target; // 一定要有返回值,不然是undefined
}
我们可以验证一下:
<script>var ret = { name: 'arbor', list: { age: 30 }};// 给任意对象扩展var obj = $.extend({}, ret);console.log(obj)
</script>
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CDJTfG6p-1577168361140)(https://cdn.nlark.com/yuque/0/2019/png/467105/1567134677301-1565f953-7af9-494d-a980-6f4c141484ca.png#align=left&display=inline&height=72&name=image.png&originHeight=144&originWidth=1024&size=24784&status=done&width=512)]
到这里,其实extend的基本逻辑已经捋清楚了。但是,如果代码是这样子的呢?结果是否符合我们的预期?
<script>var ret = { name: 'arbor', list: { age: 30 }};var res = { list: { sex: 'male' }};// 给任意对象扩展var obj = $.extend(true, {}, ret, res);console.log(obj)
</script>
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D1aPjf04-1577168361141)(https://cdn.nlark.com/yuque/0/2019/png/467105/1567135747923-e70ff4ef-719d-4ffd-bd36-4e4f3ef0c4ef.png#align=left&display=inline&height=67&name=image.png&originHeight=134&originWidth=1018&size=24585&status=done&width=509)]
从执行结果来看,按照正常思维,list对象里面应该是包含有age和sex。那是否需要一个参数去控制呢?答案是肯定的,jQuery中就提供了一种接口调用方法,当第一个对象是布尔类型时,且等于true的时候,就会去执行深拷贝,而我们上面代码中的for循环其实只是一种浅拷贝。那么,我们来想想要如何实现?
1、需要对传参的第一个变量进行判断,是否为布尔类型,如果是布尔类型,我们就需要将第二个参数作为需要扩展的对象,然后从第三个参数开始遍历,对第二参数进行扩展;
2、在遍历过程中,在上述浅拷贝的代码基础上,需要判断对象中是否已经有当前的key了,如果没有,直接object[key] = value即可,如果有,那么要拿到拥有key的对象再次去递归循环第二层。什么意思呢?其实就是在上述例子中,要使用res对第一个变量进行扩展,会发现变量上已经有list了,那就把{ sex: ‘male’ }拿到做第二次递归拷贝,看不懂没事,等会我们会分析整个执行的过程。
3、在拷贝过程中,甚至jQuery的整个实现过程中,我们可能会抽象出来一些工具类,例如判断是否为对象,或者是否为数组等,这时候就可以利用extend方法往jQuery上附加方法了。
而之说以说extend是jQuery的核心函数,这里就体现出来了:jQuery几乎所有的模块和函数都是通过extend函数扩展的。
浅拷贝只拷贝第一层,如果是基本类型就是拷贝,否则就是引用(后者会在对象修改属性时互相影响)
深、浅拷贝是针对对象、数组来说的,深拷贝是创建一个新的对象或数组,然后把需要拷贝的属性或元素添加至新建的对象或数组上,相当于把要拷贝的信息复制了一份。浅拷贝是把属性指向要拷贝的对象或数组。深拷贝的对象修改后,不会影响原对象,浅拷贝则会。
用通俗的例子来说,深拷贝就像是把张三克隆了一下,浅拷贝是给张三取了个小名,比如小张。假如克隆人摔倒了,张三不会受伤,因为他们是两个人。但如果小张摔倒了,张三也会受伤,因为他们是同一个人,只是取了两个名字而已
好了,到这里,那我们来看看代码是如何实现的吧:
// demo.html
<script>// 深拷贝、浅拷贝// 特征:第一个参数是布尔类型,true、false,否则就是浅拷贝var ret = {name:'菠菜',list:{age:10}}var res = {list:{sex:'女'}}/*浅拷贝:只做替换的关系* 在遍历ret的时候会给{}扩展一个list的属性,并把它的值赋值过去* 当遍历res的时候,会找到list的属性,并把指向的对象的引用替换掉{}里面list属性的值的引用*/var obj = $.entend({},ret,res) //obj: {name:'菠菜',list:{sex:'女'}}// 深拷贝var obj = $.entend(true,{},ret,res)
</script>// jQuery.js
(function(root) {jQuery.fn.extend = jQuery.extend = function() {// 拿到第一个参数对象var target = arguments[0] || {}var length = arguments.length;// 对任意对象进行扩展,可忽略第一个对象,因为从第二个参数开始,都是作为第一个对象的扩展var i = 1;// deep变量是用于对第一个参数进行判断,是进行深拷贝(追加)还是浅拷贝(覆盖)var deep = false;// option 需扩展对象{}后面的参数对象// key 参数对象的key// copy 参数对象的value// src 是判断需扩展对象上是否已存在key// copyIsArray 是防止多调用一次判断是否为数组的方法,简化代码var option, key, copy, src, copyIsArray, clone;if (typeof target === 'boolean') {// 判断深浅拷贝deep = target;// 将需要扩展的对象赋值为第二个target = arguments[1];// 循环从第三个开始i = 2;}if (typeof target !== 'object') {target = {};}// 如果参数只有一个,那么就是对jQuery对象或者jQuery的实例对象扩展// 例如: 1.jQuery对象的扩展 $.extend({ work: function() {}})// 2.jQuery的实例对象扩展 $.fn.extend({ sex: '男' })if (length === i) {// 这里的this等于函数调用的对象本身,即jQuery对象或者jQuery的实例对象target = this;// 这里的用意是拿到jQuery内置的一些方法,例如:i--;//让i的值变为0}// 深浅拷贝// 给任意对象进行扩展的逻辑,例如$.extend({}, {name: 'arbor'});for(; i < length ; i++) {// option:需要遍历的属性的值if ((option = arguments[i]) !== null) {for(key in option) {// 将第二个参数之后的值赋值给第一个对象// 注意如果有多个参数,参数的key相同的话,会实现覆盖// target[key] = option[key];copy = option[key];// 首次遍历的时候src可能是undefined,因为第二个参数值可能是空{}src = target[key];// 判断是深浅拷贝,有值是深拷贝,并且后面的参数是Object还是Array类型if (deep && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {if (copyIsArray) {// 如果是数组// 每次都要重置copyIsArray = false;// src有值并且是数组就直接赋值,否则就创建空数组clone = src && jQuery.isArray(src) ? src : [];} else {// 如果是对象// src有值并且是对象就直接赋值,否则就创建空对象clone = src && jQuery.isPlainObject(src) ? src : {};}// 做一次浅拷贝target[key] = jQuery.extend(deep, clone, copy);} else if (copy !== undefined) { //判断浅拷贝是否有值target[key] = copy;}}};}return target;}// 共享原型对象jQuery.fn.init.prototype = jQuery.fn;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)
注意到36行的i–没有,如果我们是往jQuery本身附加一些属性或者方法时,循环是需要从下标为0开始的,而不能从初始化的下标为1开始。
62行是核心代码,就是对存在多层级的对象或者数组递归地去拷贝,从而达到深拷贝的效果。
最后记得return出来,否则你拿到的肯定是undefined。
最后,我们再来捋一下执行过程吧:
// for循环中
deep = true;
i = 2;
// 1.首层循环
option = ret = { name: 'arbor', list: { age: 30 }};// 1.1循环option对象的key和value,即name: 'arbor'key = 'name'copy = option[key] = 'arbor';src = target[key] = undefined;// 判断走else-iftarget[key] = copy// 此时的target的值target: {name: 'arbor',}// 1.2 for..in继续循环list: { age: 30 }key = 'list'copy = option[key] = { age: 30 };src = target[key] = undefined;// 判断走ifcopyIsArray = false;clone = {};// 递归执行jQuery.extend,进行浅拷贝target[key] = jQuery.extend(true, {}, { age: 30 }) = { age: 30 }// 此时的target的值, 执行1.2.1得到的值target: {name: 'arbor',list: { age: 30 }}// 1.2.1 listoption = { age: 30 }key = 'age'copy = option[key] = 30;src = target[key] = undefined;// 判断走else-iftarget[key] = copy// 此时的target的值target: {age: 30,}// 子循环结束,return target// 2.首层循环
deep = true;
i = 2;
option = res = { list: { sex: 'male' }};// 2.1 for..in继续循环list: { sex: 'male' }key = 'list'copy = option[key] = { sex: 'male' };src = target[key] = { age: 30 };// 判断走ifcopyIsArray = false;clone = { age: 30 };// 递归执行jQuery.extend,进行浅拷贝target[key] = jQuery.extend(true, { age: 30 }, { sex: 'male' }) = { sex: 'male' }// 此时的target的值, 执行2.1.1得到的值,此时return最终的targettarget: {name: 'arbor',list: { age: 30, sex: 'male' }}// 2.1.1 listoption = { sex: 'male' }key = 'sex'copy = option[key] = 'male';src = target[key] = undefined;// 判断走else-iftarget[key] = copy// 此时的target的值target: {sex: 'male',}// 子循环结束,return target
至此,jQuery的整体架构和核心功能就到一段落了。








![[转]2d游戏开发:游戏地图编辑器](http://image.jqcq.com/upload/2006-06/15/11503525054.png)






