首页
Search
1
解决visual studio code (vscode)安装时没有选择安装路径问题
320 阅读
2
如何在 Clash for Windows 上配置服务
215 阅读
3
Linux 下 Bash 脚本 bad interpreter 报错的解决方法
150 阅读
4
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
149 阅读
5
uniapp打包app提示通讯录权限问题,如何取消通讯录权限
113 阅读
clash
服务器
javascript
全部
游戏资讯
登录
Search
加速器之家
累计撰写
1,195
篇文章
累计收到
0
条评论
首页
栏目
clash
服务器
javascript
全部
游戏资讯
页面
搜索到
758
篇与
的结果
2024-10-21
取其精华,去其糟粕
今天差不多读完了《JavaScript 语言精粹》,虽然这本书写的比较早,当时还是 ES3 的标准,不过依然还是有很多借鉴意义的。有些事情在当时的环境和浏览器的性能下,还是比较流行的,但是现在却是另一种情况了。比如数组转换为字符串,那时候 IE6~IE8 还比较盛行的时候,更多的是推荐Array.join('')来进行转换,但是现在,更加推荐使用+进行字符串的拼接。不过说实话,虽然书名是《JavaScript 语言精粹》,但是读完后,除了记住 js 中各种各样的糟糕状况,还真没想起几个精华来。JavaScript 作为一个 10 天就设计出来的语言,承受了超乎想象的流行程度。它一诞生,就在短到令人吃惊的时间里被全世界所接受。它从来没有在实验室里被试用和打磨。当它还非常粗糙时,它就被直接集成到网景的浏览器中。虽然 java 的小应用程序(java applets)的失败,JavaScript 变成了默认的‘web 语言’。不过,JavaScript 还是多多少少有其自身的特点的。比如强大的对象字面量,原型继承,强大的数组和字符串操作等。好吧,精华还真想不出什么来,谁让它在目前来说是唯一的一个脚本编程语言呢。JavaScript 的糟粕还是不少的。比如:1.全局变量:全局变量可以被程序的任何部分在任意时间修改,这些都是不可控的。本来你一直这么使用全局变量的,可是其他人根据他的需要修改了全局变量的值,就会导致你的程序出现 Bug。针对这种情况,我们需要尽量地避免使用全局变量,如果非得需要不可的话,我们可以把全局变量放到一个命名空间里,当其他人声明全局变量时,要么使用你的命名空间,要么使用它的命名空间,这样就能尽量地减少全局变量的重复了:var Wenzi = Wenzi || {}; Wenzi.WEBBASEURL = 'www.xiabingbao.com'; Wenzi.SERVER_TIME = 1431871979; 2.作用域:JavaScript 提供了块级语法,却没有提供块级作用域,在该作用域内声明的变量,在其外部也能够使用。比如:for (var i = 0; i < 10; i++) {} console.log(i); // 输出10 3.自动添加分号:使用 JavaScript 进行编程,不用担心末尾没有写分号而导致程序不能运行。可是如果没有良好的编程习惯,就会产生很严重的逻辑错误。在下面的代码中,我们本意是要返回一个 json 结构,可是因为 js 自动添加了分号,导致返回了一个空。return; { name: 'wenzi'; } 4.+的使用:+运算符既可以用作拼接字符串,也可用作数字的相加。这个符号产生什么作用,取决于两端的云算数的类型:若两端的云算数都是数字,那么就是两个数的相加;若其中一个为字符串或两个都为字符串,那么就是字符串的拼接;若是其他的类型,那么就将其转换为字符串进行拼接。...其实 JavaScript 中还有相当多的糟粕,目前就暂时总结这几种吧。也可以看看之前总结的【Javascript 的严格模式】,在严格模式下,能预防很多低级错误的发生。虽然 JavaScript 有着这样的不好那样的不好,但是,正是因为它的这些特性,让 web 的脚本编程更加的丰富。web 前端发展到现在,真是处于百花齐放的盛况:jQuery, augular, node, react 等。而且现在还有 CMD 和 AMD 规范。到这里,或许会有人问我,这本书值不值得读,值不值得买。我可以很肯定的说:‘值得买’,但是,不是所有人都需要买。就我目前读完的感觉,这本书比较适合学习 6~12 个月的学习者购买。若学习 javascript 时间太短,书里说的那些知识点可能都没有接触到;若学习的时间稍微长一些,比如 2 年左右的(如本人),购买的价值就下降了,因为书上很多的精华与糟粕,其实在你的项目中早已经接触到了;而且,若你一直使用 ES5 的严格模式进行开发,就能避免很多潜在的问题。
2024年10月21日
15 阅读
0 评论
0 点赞
2024-10-21
html5实现图片预览和查看原图
html5 从一开始就给开发者很多的期待,提供众多新的 API,不用再想以前一样,为了实现某个功能写很多的代码。在以前,如果要实现图片预览会怎么做呢,因为为了安全的原因,web 端的 js 是不能读取文件的本地真实路径的,那么只能将图片上传到服务器上,然后再拿到图片的链接,这样才能实现图片预览。而服务器呢,比如有两个文件夹,一个是临时文件夹,一个是正式文件夹,临时文件夹会定时进行清理,正式文件夹是用户确认使用的图片存储的位置。 1. fileReader # 现在 html5 提供的 API 不再让图片预览那么麻烦,FileReader 提供了很多的方法来进行图片预览和文本读取,同时也提供了一整套完整的事件来捕获文件的状态,如下:FileReader 接口的方法 FileReader 接口有 4 个方法,其中 3 个用来读取文件,另一个用来中断读取。无论读取成功或失败,方法并不会返回读取结果,这一结果存储在 result 属性中。 方法名 参数 描述 readAsBinaryString file 将文件读取为二进制编码 readAsText file[, encoding] 按照格式将文件读取为文本,encode默认为UTF-8 readAsDataURL file 将文件读取为DataUrl abort (none) 终端读取操作 FileReader 接口事件 FileReader 接口包含了一套完整的事件模型,用于捕获读取文件时的状态。 事件 描述 onabort 中断 onerror 出错 onloadstart 开始 onprogress 正在读取 onload 成功读取 onloadend 读取完成,无论成功失败 2. 使用 fileReader 读取图片 # 从上面的表格中,我们可以大致了解 fileReader 提供哪些方法和事件,不过本文主要是讲解图片的读取,那么我们就是用readAsDataURL()就可以了。不过,在进行这一切之前,我们必须检测当前的浏览器是否支持 html5 的 fileReader,别进行了一系列的处理和操作,结果 js 报错,说 fileReader 没有定义。就好像对一个女孩儿又亲又啃,马上要提枪上马了,结果发现这是个纯爷们。if (!(window.FileReader && window.File && window.FileList && window.Blob)) { show.innerHTML = '您的浏览器不支持fileReader'; upimg.setAttribute('disabled', 'disabled'); return false; } 好的,让我们先看下 demo 演示:【狠狠点击这里】 2.1 读取单张图片 # 使用 input[type=file]控件读取文件,然后监听这个控件的 change 事件,若读取的文件个数大于零,那么就进行下一步的操作: var upimg = document.querySelector('#upimg'); upimg.addEventListener('change', function (e) { var files = this.files; if (files.length) { // 对文件进行处理,下面会讲解checkFile()会做什么 checkFile(this.files); } }); 现在我们只能选取一张图片,针对选取的这张图片,我们使用 fileReader 进行图片的处理// 图片处理 function checkFile(files) { var file = files[0]; var reader = new FileReader(); // show表示,用来展示图片预览的 if (!/image\/\w+/.test(file.type)) { show.innerHTML = '请确保文件为图像类型'; return false; } // onload是异步操作 reader.onload = function (e) { show.innerHTML = ''; }; reader.readAsDataURL(file); } 现在,就可以在页面上看到图片了。审查元素后我们能够看到,图片地址是个 base64 的字符串,如:'data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sA......' 2.2 读取多张图片 # 多张图片和单张图片的处理过程很相似,但是也还是有区别的,因为 reader.onload()是一个异步的操作,进行下一步的操作时必须在这个方法里 // change事件没有改动 // 图片处理 function checkFile(files) { var html = '', i = 0; var func = function () { if (i >= files.length) { // 若已经读取完毕,则把html添加页面中 show.innerHTML = html; } var file = files[i]; var reader = new FileReader(); // show表示,用来展示图片预览的 if (!/image\/\w+/.test(file.type)) { show.innerHTML = '请确保文件为图像类型'; return false; } reader.onload = function (e) { html += ''; i++; func(); //选取下一张图片 }; reader.readAsDataURL(file); }; func(); } 2.3 拖拽拉取图片 # 拖拽事件,采用的是 html5 中的 drag 和 drop,本文不着重介绍这两个方法,仅仅是讲解如何使用。首先,我们设置一块拖拽区域,告诉用户应该把图片拖拽到什么位置: .drag{ width: 400px;height: 100px;border: 1px dotted #333; text-align: center; line-height: 100px; color: #aaa; display: inline-block;} .drag_hover{background: #FAD6F9;} 拖拽区域 然后,我们给 drag 区域绑定上拖拽事件var drag = document.getElementById('drag'); drag.addEventListener( 'dragenter', function (e) { // 拖拽鼠标进入区域时 this.className = 'drag_hover'; }, false ); drag.addEventListener( 'dragleave', function (e) { // 拖拽鼠标离开区域时 this.className = ''; }, false ); drag.addEventListener( 'drop', function (e) { // 当鼠标执行‘放’的动作时,执行读取文件操作 var files = e.dataTransfer.files; this.className = ''; if (files.length != 0) { checkFile(files); } e.preventDefault(); }, false ); drag.addEventListener( 'dragover', function (e) { // 当对象拖动到目标对象时触发 e.dataTransfer.dragEffect = 'copy'; e.preventDefault(); }, false ); 这里有个需要注意的地方:需要给 dragover 和 drop 添加阻止默认事件,否则浏览器会采用file:///的方式打开文件。drop 事件执行后就是进行 checkFile(),后续的操作与使用 input[type=file]的操作一样。 3. 点击查看原图 # 当我们点击图片查看原图时,需要知道图片的原始尺寸。可能你会想到使用 img.width 和 img.height,对,这个确实能获取到图片的长和宽,但是,这个长和宽是经过 css 修饰后的,不是图片原始的尺寸。如果要获取图片的原始尺寸,我们可以在 js 中创建一个 Image 对象,然后把那张图片的地址给了这个 Image 对象,然后获取 Image 对象的尺寸,这样就能获取到图片的原始尺寸了。var img = new Image(); img.src = img.src; // 给新的img对象链接 console.log(img.width, img.height); 而在 html5 中,我们不用再那么麻烦的创建一个无用的 img 对象了,直接使用给出的属性即可。console.log(img.naturalWidth); // 获取图片的原始的宽度 console.log(img.naturalHeight); // 获取图片的原始的高度 获取到图片的原始尺寸后,就能做出‘查看原图’的效果了。 4. 总结 # html5 真是个好东西,还有着很多的东西等着我们去挖掘。“蹿腾吧,程序员”!
2024年10月21日
31 阅读
0 评论
0 点赞
2024-10-21
javascript设计模式之构造函数模式
在学习设计模式之前,我们首先要确定几个要点,应该从哪几个方向研究设计模式,当我们搞清楚这个设计模式后,我们应该在什么场合使用这个模式: 模式的名称和实现方式; 模式的适用场景,即这个模式是为了解决什么问题而设计出来的; 模式的整体结构是什么,我该怎么使用(有的模式有改进型,混合型等各种变形); 该模式与其他模式的比较 总结,模式的优点与缺点 下面我们就根据这几个点开始学习 1. 模式的名称和实现方式 # 本文研究的是构造函数模式,名称当时构造函数(Constructor)模式了。构造器模式用于创建特定类型的对象——准备好对象以备使用,同时接受构造函数可以使用的参数,以在第一次创建对象时,设置成员属性和方法的值。既然是构造函数模式,那就必然是以构造函数为基础的模式。如Array(), String(), Object()等js本来提供的构造函数,也是能形成构造函数模式的。var arr = new Array(1, 2, 3, 4); var str = new String('this is a string'); var obj = new Object({ 'name':'wenzi', 'age':'25', 'sex':'male' }) 当然,js里提供了这些构造函数的字面量声明的简单方式,不用再那么麻烦的new了,而且更加推荐使用以下的方式进行构建。var arr = [1, 2, 3, 4]; var str = 'hello world'; var obj = { 'name':'wenzi', 'age':'25', 'sex':'male' } 同时,我们也可以使用自己定义的构造函数,一般构造函数的首字母大写,这是一种良好的编码规范。如:function Person(name, age){ this.name = name; this.age = age; this.getName = function(){ return this.name; } } var parent = new Person('jack', 46); var son = new Person('jack', 21); 2. 模式的适用场景 # 构造函数模式通常使用在对象的属性较少,相对来说比较简单的地方,比如仅仅需要包装出一个对象,或者想用一个变量存储几个属性。 3. 模式的整体结构 # 模式的整体刚才在第1部分讲解了一下,主要有:原生构造函数,对象字面量,自定义构造函数等。在上面的自定义构造函数中,我们可以确认parent和son两个属性值的比较结果parent.name === son.name; // true parent.age === son.age; // false 可是parent.getName===son.getName会输出什么呢?运行之后,我们就会发现,这个比较式会输出false。为什么呢?函数名一样,都是getName,而且函数体也一样,可是为什么会输出false呢?其实,每次在使用Person进行对象创建的时候,都会创建一个新的函数给getName,因此,虽然看起来是一样,但实际上是两个函数。如果创建的对象比较多的话,那么创建的getName函数也会比较的多,可是getName的功能是一样的,没必要跟着也创建好多次。因此我们可以做如下的改进:function Person(name, age){ this.name = name; this.age = age; this.getName = getName; } function getName(){ return this.name; } var parent = new Person('jack', 46); var son = new Person('jack', 21); parent.getName === son.getName; // true 这样使用 Person 创建的对象使用getName时,都会将引用指向getName()这个函数。因此,上面的比较式会输出出。这样写能够减少getName的创建次数,减少内存的使用。不过其实这种方式,也是有缺点的,如果构造函数里的方法很多呢,那是不是得写好多全局的函数,用来引用呢。还有一种方式是,把方法写在原型prototype上,调用js构造器创建一个对象后,新对象就会具有构造器原型的所有属性。function Person(name, age){ this.name = name; this.age = age; } Person.prototype.getName = function(){ return this.name; } var parent = new Person('jack', 46); var son = new Person('jack', 21); parent.getName === son.getName; // true 针对这三种方式,应该考虑实际情况使用,而不是一味的采用第三种方式。如果构造函数相对来说比较简单,而且创建出的对象也不多,也就没比较使用原型。 4. 总结 # 为什么没有和其他模式的比较呢?因为还没有开始其他模式的讲解,哈哈!构造函数模式的优点就是构造简单,使用方便,而且容易理解。当然缺点也是比较明显的,就是扩展性差,每个实例的方法都是独立的,多数情况下同个对象的实例方法都是一样的。
2024年10月21日
6 阅读
0 评论
0 点赞
2024-10-21
javascript中的闭包
1. 简要介绍 # 闭包可谓是js中的一大特色了,即使你对闭包没概念,你可能已经在不知不觉中使用到了闭包。闭包是什么,闭包就是一个函数可以访问到另一个函数的变量。这就是闭包,解释起来就这么一句话,不明白?我们来看一个简单的例子:function getName(){ var name='wenzi'; setTimeout(function(){ console.log(name); }, 500); } getName(); 这就其实已经是闭包了,setTimeout中的function是一个匿名函数,这个匿名函数里的name是geName()作用域中的变量,匿名函数里只有一个输出语句:console.log();还有一个很经典的例子也可以帮助我们理解什么是闭包:function create(){ var i=0; // 返回一个函数,暂且称之为函数A return function(){ i++; console.log(i); } } var c = create(); // c是一个函数 c(); // 函数执行 c(); // 再次执行 c(); // 第三次执行 在上面的例子中,create()返回的是一个函数,我们暂且称之为函数A吧。在函数A中,有两条语句,一条是变量i自增(i++),一条是输出语句(console.log)。第一次执行执行c()时会产生什么样的结果?嗯,输出自增后的变量i,也就是输出1;那么第二次执行c()呢,对,会输出2;第三次执行c()时会输出3,依次累加。这个create()函数依然满足了我们在刚开始时的定义,函数A使用到了另一个函数create()中的变量i。可是为什么会产生这样的输出呢,为什么i就能一直自增呢,create函数已经执行完并返回结果了呀,可是为什么还能接着使用i呢,而且i还能自增。这里就涉及到了三个比较重要的概念,讲解完这三个概念,我们对闭包就可以有一个比较好的理解了。 2. 三个重要概念 # 2.1 执行环境与变量对象 # 执行环境是JavaScript中一个重要的概念,它决定了变量或函数是否有权访问其他的数据,决定了它们各自的行为。每个执行环境都有一个与之对应的变量对象,执行环境中定义的所有变量和函数都保存在这个对象中。虽然我们的代码无法访问这个对象,但是解析器在处理数据时会在后台使用它。我们用一个比较简单的比喻来形容这两个概念。执行环境就是一个人,变量对象就是这个人的身份证号,每个人都有其对应的身份证号。他这个人决定了他身上的很多属性和方法(动作),而且这个人的属性和方法都在他的身份证号上,当这个人消亡的时候,身份证号也就随之就注销了。全局执行环境是最外层的一个执行环境。在web浏览器中,全局执行环境被认为是window对象,因为所有的全局变量和全局函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或者浏览器——时才会被销毁),被垃圾回收机制回收。每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入到一个环境栈中。而在函数执行后,栈将其环境弹出,把控制权返回给之前的执行环境。 2.2 作用域链 # 作用域链是当代码在一个环境中执行时创建的,作用域链的用途就是要保证执行环境中能有效有序的访问所有变量和函数。作用域链的最前端始终都是当前执行的代码所在环境的变量对象,下一个变量对象是来自其父亲环境,再下一个变量对象是其父亲的父亲环境,直到全局执行环境。标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直到找到标识符为止(如果找不到标识符,通常会导致错误发生)。其实,通俗的理解就是:在本作用域内找不到变量或者函数,则在其父亲的作用域内寻找,再找不到则到父亲的父亲作用域内寻找,直到在全局的作用域内寻找! 2.3 垃圾回收机制 # 在js中有两种垃圾收集的方式:标记清除和引用计数。标记清除:垃圾收集器在运行时会给存储在内存中的所有变量都加上标记(具体的标记方式暂时就不清楚了),待变量已不被使用或者引用,去掉该标记或添加另一种标记。最后,垃圾收集器完成内存清除工作,销毁那些已无法访问到的这些变量并回收他们所占用的空间。引用计数:一般来说,引用计数的含义是跟踪记录每个值被引用的次数。当声明一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数便是1,如果同一个值又被赋给另一个变量,则该值的引用次数加1,相反,如果包含对这个值引用的变量又取得了另一个值,则这个值的引用次数减1。当这个值的引用次数为0时,说明没有办法访问到它了,因而可以将其占用的内存空间回收。除了一些极老版本的IE,目前市面上的JS引擎基本采用标记清除来除了垃圾回收。但是需要注意的是IE中的DOM由于机制问题,是采用了引用计数的方式,所以会有循环引用的问题,造成内存泄露。var ele = document.getElementById(“element”); var obj = new Object(); ele.obj = obj; // DOM元素ele的obj引用obj变量 obj.ele = ele; // obj变量的ele引用了DOM元素ele 这样就造成了循环引用的问题,导致垃圾回收机制回收不了ele和obj。不过,可以在不使用ele和obj时,对这两个变量进行 null 赋值,然后垃圾回收机制就会回收它们了。 3. 理解闭包 # 在第2部分讲解了三个重要的概念,这三个概念有助于我们更好的理解闭包。我们再次拿出上面的这个例子:function create(){ var i=0; // 返回一个函数,暂且称之为函数A return function(){ i++; console.log(i); } } var c = create(); // c是一个函数,即函数A c(); // 函数执行 c(); // 再次执行 c(); // 第三次执行 从上面的“每个函数都有自己的执行环境”可以知道:create()函数是一个执行环境,函数A也是一个执行环境,且函数A的执行环境在create()的里面。这样就形成了一个作用域链:window->create->A。当执行c()时,函数A就会首先在当前执行环境中寻找变量i,可是没有找到,那么只能顺着作用域链向后找;OK,在create()的执行环境中找到了,那么就可以使用了变量i了。可是我们还有一个疑问,按照上面的说法,函数create()执行完毕后,create()不再有其他的操作,这个函数与里面的变量和方法应该被回收了呀,可是为什么函数c()多次执行时依然能够输出变量i呢。这就是闭包的独特之处。函数create()执行完毕后被垃圾回收机制进行回收,虽然它的作用域链会被销毁,即不再存在window->create这个链式关系,但是函数A()[c()]的作用域链还依然引用着create()的变量对象,还存在着window->create->A的链式关系,导致垃圾回收机制不能回收create()的变量对象,create()的变量对象仍然停留在内存中,直到函数A()[c()]被销毁后,create()的变量对象才会被销毁。因此,虽然create()已经执行完毕了,但是create()的变量对象并没有被回收,还停留在内存中,依然可以使用。有些同学对变量i的变化可能还有一些疑问“每次执行c()时,应该都是向上一级寻找变量i,每次寻找到时变量i应该都是0呀,为什么会自增呢?”。在这里再详细的解释一下,若当前作用域没有这个变量时,就会向上一级的作用域进行寻找,一直到能寻找到该变量(或出错为止),当寻找这个变量后,当前的作用域就会保存这个变量的引用。因此,若下次使用这个变量时,就会直接使用,而且使用的是同一个变量i。所以,上面多次运行c()后,能够使i自增,而不是每次都获取到0。从上面的讲解中我们可以看到,闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。当页面中存在过多的闭包,或者闭包的嵌套很多很深时,会导致内存占用过多。因此,在这里建议:慎用闭包。 4. 闭包与变量 # 有很多新手为DOM元素绑定事件时,通常会这么写:function bindClick(){ var li = document.getElementsByTagName('li'); // 假设一共有5个li标签 for(var i=0; ibindClick->匿名函数。可是这跟输出的i有关系么?有。作用域链中的每个变量对象保存的是对变量和方法的引用,而不是保存这个变量的某一个值。当执行到匿名函数时,bindClick()其实已经执行完毕了,变量i的值就是5,此时每个匿名函数都引用着同一个变量i。不过我们稍微修改一下,以满足我们的预期:/* // 错误,onclick绑定的是立即执行函数的返回值, // 而此立即执行并没有返回值,也就是onclick = undefined function bindClick(){ var li = document.getElementsByTagName('li'); for(var i=0; i
2024年10月21日
5 阅读
0 评论
0 点赞
2024-10-21
jQuery 中 extend 的实现
通常我们使用 jquery 的 extend 时,大都是为了实现默认字段的覆盖,即若传入某个字段的值,则使用传入值,否则使用默认值。如下面的代码:function getOpt(option) { var _default = { name: "wenzi", age: "25", sex: "male", }; $.extend(_default, option); return _default; } getOpt(); // {name: "wenzi", age: "25", sex: "male"} getOpt({ name: "bing" }); // {name: "bing", age: "25", sex: "male"} getOpt({ name: "bing", age: 36, sex: "female" }); // {name: "bing", age: 36, sex: "female"} 那现在我们就得需要知道这个 extend 具体是怎么实现的了,除了实现上面的功能,还有其他作用么?那肯定是有的啦,否则我也不会问那句话了((⊙﹏⊙)b)。我们先来看看 extend 主要有哪些功能,然后再看实现这些功能的原理。 1. extend 能实现的功能 # 其实从 extend 的含义里,我们就能知道 extend 是做什么的了。extend 翻译成汉语后就是:延伸、扩展、推广。 1.1 将两个或更多对象的内容合并到第一个对象 # 我们来看看$.extend()提供的参数:jQuery.extend( target [, object1 ] [, objectN ] ),extend 方法需要至少传入一个参数,第一个必需,后面的都是可选参数。若传给 extend 是两个或两个以上的参数都是对象类型,那么就会把后面所有对象的内容合并给 target(第一个对象)上。我们再来看看上面的例子:function getOpt(option) { var _default = { name: "wenzi", age: "25", sex: "male", }; $.extend(_default, option); return _default; } $.extend()中接收了两个参数 _default 和 option,那么 extend 方法执行时,就会把 option 对象上字段的值全给了 _default。于是 _default 上设置的默认值就会被 option 上的值覆盖。当然,若 option 上没有这个字段,就不会覆盖 _default 上字段的值。上面函数中的 extend,只是传入了两个参数,那传的参数再更多一些呢: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" }, }; // {name: "obj2", age: "67", sex: {error: "sorry, I dont't kown"}} getOpt(_default, obj1, obj2, obj3); 这里我们传入了 4 个参数,然后 getOpt()返回第一个参数的值。从运行的得到结果我们可以看到,属性值永远是最后一个属性的值。还有很重要的一点,$.extend()其实是有返回值的,返回的就是修改后的第一个参数的值。如我们可以把上面的函数修改成这样:function getOpt(target, obj1, obj2, obj3) { var result = $.extend(target, obj1, obj2, obj3); return result; // // result即修改后的target值 } 若我们传入的参数不想被修改,我们可以用一个空对象来作为第一个参数,然后获取$.extend()的返回值:function getOpt(target, obj1, obj2, obj3) { var result = $.extend({}, target, obj1, obj2, obj3); return result; // // result即为{}修改后的值 } 1.2 为 jQuery 扩展方法或属性 # 刚才我们在 1.1 中讲的$.extend()的例子都是传了两个或两个以上的参数,但其实只有一个参数是必须的。若只传一个参数会怎样呢。 如果只有一个参数提供给$.extend(),这意味着目标参数被省略。在这种情况下,jQuery 对象本身被默认为目标对象。这样,我们可以在 jQuery 的命名空间下添加新的功能。这对于插件开发者希望向 jQuery 中添加新函数时是很有用的。 举个例子:$.extend({ _name: "wenzi", _getName: function () { return this._name; }, }); $._name; // wenzi $._getName(); // wenzi 这样我们就为 jQuery 扩展了_name属性和_getName方法。 1.3 深度拷贝和浅度拷贝 # 针对什么是深度拷贝,什么是浅度拷贝,我们先来看一个简单的例子。var obj = { name: "wenzi", sex: "male" }; var obj1 = obj; // 赋值 obj1.name = "bing"; console.log(obj.name); // bing 我们修改了 obj1 中的 name 值,结果 obj 中的值也跟着发生了变化,这是为什么呢。其实这就是 浅度拷贝 :这仅仅是将 obj 对象的引用地址简单的复制了一份给予变量 obj1,而并不是将真正的对象克隆了一份,因此 obj 和 obj1 指向的都是同一个地址。当修改 obj1 的属性或给 obj1 添加新属性时,obj 都会受到影响。可是如果变量的值不是对象和数组,修改后面的变量是不会影响到前面的变量:var s = "hello"; var t = s; t = "world"; console.log(s); // hello 那么深度拷贝就不是拷贝引用地址,而是实实在在的复制一份新对象给新的变量。 在上面使用$.extend()中,都是使用的浅度拷贝,因此若后面的参数值是 object 类型或 array 类型,修改_default(target)的值,就会影响后面参数的值。如我们使用getOpt(_default, obj1, obj2, obj3);得到的_default 值是{name: "obj2", age: "67", sex: {error: "sorry, I dont't kown"}},可是若:_default.sex.error = "hello world"; 那么 obj3.sex.error 也会跟着修改,因为 obj3.sex 是一个 object 类型。不过$.extend()也提供了深度拷贝的方法:jQuery.extend( [deep ], target, object1 [, objectN ] )。若第一个参数是 boolean 类型,且值是 true,那么就会把第二个参数作为目标参数进行合并。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 执行后我们发现,无论怎么修改 obj.score 里的值,都不会影响到 obj1.score 了。 2. jQuery 中 extend 实现原理 # 其实不看源码,对 extend 大致的过程应该也是了解的:对后一个参数进行循环,然后把后面参数上所有的字段都给了第一个字段,若第一个参数里有相同的字段,则进行覆盖操作,否则就添加一个新的字段。下面是 jQuery 中关于 extend 的源码,我就在源码上进行注释讲解了,随后再在后面进行总结:// 为与源码的下标对应上,我们把第一个参数称为`第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; // 默认为浅度拷贝 // 判断第0个参数的类型,若第0个参数是boolean类型,则获取其为true还是false // 同时将第1个参数作为目标参数,i从当前目标参数的下一个 // Handle a deep copy situation if (typeof target === "boolean") { deep = target; // Skip the boolean and the target target = arguments[i] || {}; i++; } // 判断目标参数的类型,若目标参数既不是object类型,也不是function类型,则为目标参数重新赋值 // Handle case when target is a string or something (possible in deep copy) if (typeof target !== "object" && !jQuery.isFunction(target)) { target = {}; } // 若目标参数后面没有参数了,如$.extend({_name:'wenzi'}), $.extend(true, {_name:'wenzi'}) // 则目标参数即为jQuery本身,而target表示的参数不再为目标参数 // Extend jQuery itself if only one argument is passed if (i === length) { target = this; i--; } // 从第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) { // 使用for~in获取该参数中所有的字段 // Extend the base object for (name in options) { src = target[name]; // 目标参数中name字段的值 copy = options[name]; // 当前参数中name字段的值 // 若参数中字段的值就是目标参数,停止赋值,进行下一个字段的赋值 // 这是为了防止无限的循环嵌套,我们把这个称为,在下面进行比较详细的讲解 // Prevent never-ending loop if (target === copy) { continue; } // 若deep为true,且当前参数中name字段的值存在且为object类型或Array类型,则进行深度赋值 // Recurse if we're merging plain objects or arrays if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) { // 若当前参数中name字段的值为Array类型 // 判断目标参数中name字段的值是否存在,若存在则使用原来的,否则进行初始化 if (copyIsArray) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src : []; } else { // 若原对象存在,则直接进行使用,而不是创建 clone = src && jQuery.isPlainObject(src) ? src : {}; } // 递归处理,此处为2.2 // Never move original objects, clone them target[name] = jQuery.extend(deep, clone, copy); // deep为false,则表示浅度拷贝,直接进行赋值 // 若copy是简单的类型且存在值,则直接进行赋值 // Don't bring in undefined values } else if (copy !== undefined) { // 若原对象存在name属性,则直接覆盖掉;若不存在,则创建新的属性 target[name] = copy; } } } } // 返回修改后的目标参数 // Return the modified object return target; }; 源码分析完了,下面我们来讲解下源码中存在的几个难点和重点。 2.1 若参数中字段的值就是目标参数,停止赋值 # 在源码中进行了一下这样的判断:// Prevent never-ending loop if ( target === copy ) { continue; } 为什么要有这样的判断,我们来看一个简单的例子,如果没有这个判断会怎么样:var _default = { name: "wenzi" }; var obj = { name: _default }; $.extend(_default, obj); console.log(_default); 输出的_default 是什么呢:_default = { name: _default }; _default 是 object 类型,里面有个字段 name,值是_default,而_default 是 object 类型,里面有个字段 name,值是_default......,无限的循环下去。于是 jQuery 中直接不进行操作,跳过这个字段,进行下一个字段的操作。 2.2 深度拷贝时进行递归处理 # 我们在前面稍微的讲解了一下,变量值为简单类型(如 number, string, boolean)进行赋值时是不会影响上一个变量的值的,因此,如果当前字段的值为Object或Array类型,需要对其进行拆分,直到字段的值为简单类型(如 number, string, boolean)时才进行赋值操作。 3. $.extend()与$.fn.extend() # 上面讲解的全都是$.extend(),根本就没讲$.fn.extend()。可是,你有没有发现一个细节,在这段代码的第一行是怎么写的:jQuery.extend = jQuery.fn.extend = function () {}; 也就是说$.extend()与$.fn.extend()共用的是同一个函数体,所有的操作都是一样的,只不过两个 extend 使用的对象不同罢了:$.extend()是在 jQuery($)上进行操作的;而$.fn.extend()是在 jQuery 对象上进行操作的,如$('div').extend(). 4. 总结 # 这就是 jQuery 中 extend 的实现,以后若我们需要用到上面的功能时,除了使用$.extend(),我们也可以在不引入 jQuery 框架的情况下,自己写一个简单的 extend()来实现上面的功能。
2024年10月21日
4 阅读
0 评论
0 点赞
1
...
39
40
41
...
152