day-064-sixty-four-20230507-jQuery源码-循环处理-this处理-迭代对象方法each的封装
jQuery源码
多库共存命名冲突
-
jQuery及$的命名冲突
// 多个使用到了$符的库共存,及多版本jQuery共存。 var _jQuery = window.jQuery; //在覆盖的情况下映射jQuery。 var _$ = window.$; //在$被覆盖的情况下映射。 jQuery.noConflict = function (deep) { // 校验当前用的jQuery版本是自己这一版,也就是意味着当前作用域下的$就是之前window上的旧window.$,再进行转移。 if (window.$ === jQuery) { window.$ = _$; } // 深度转移-如果传入了deep且为true。校验当前用的jQuery版本是自己这一版,也就是意味着当前作用域下的_jQuery就是之前window上的旧window.jQuery,再进行转移。 if (deep && window.jQuery === jQuery) { window.jQuery = _jQuery; } return jQuery; };
-
转移
_
的使用权// 转移“_”的使用权,可以用另一个变量来接收当前的utils,以便可以正常使用。 let origin = null; origin = window._; const noConflict = function noConflict() { if (window._ === utils) { window._ = origin; } return utils; }; window._=utils;//utils是核心工具对象。
- lodash 是目前项目中常用的开源工具库,以
_
为简写`
- lodash 是目前项目中常用的开源工具库,以
简化创建实例对象的流程
-
使用
$('body')
与new $('body')
的效果一致,都会返回一个jQuery实例对象。-
要实现这个效果,就要用一个中转的构造函数,它返回一个jQuery实例对象。
- 因为这个中转的构造函数,内部可能要使用到this,否则直接在jQuery函数中返回一个原型为jQuery.prototype的实例对象应该也可以。
// 我们在外面使用的JQ选择器 “ $('...') ”,其实就是把 jQuery 函数执行 var jQuery = function (selector, context) { return new jQuery.fn.init(selector, context); }; // JQ的原型对象 jQuery.fn = jQuery.prototype = { constructor: jQuery, // ... }; // init构造函数 var init = jQuery.fn.init = function (selector, context, root) { // ... }; init.prototype = jQuery.fn;
-
-
基础的骨架搭建
(function (global, factory) { "use strict"; if (typeof module === "object" && typeof module.exports === "object") { module.exports = global.document ? factory(global, true) : function (w) { if (!w.document) { throw new Error("jQuery requires a window with a document"); } return factory(w); }; } else { factory(global); } })( typeof window !== "undefined" ? window : this, function factory(window, noGlobal) { // 我们在外面使用的JQ选择器 “ $('...') ”,其实就是把 jQuery 函数执行 var jQuery = function (selector, context) { return new jQuery.fn.init(selector, context); }; // JQ的原型对象 jQuery.fn = jQuery.prototype = { constructor: jQuery, // ... }; // init构造函数 var rootjQuery = jQuery(document), init = (jQuery.fn.init = function (selector, context, root) { // ... }); init.prototype = jQuery.fn; /* 暴露API */ if (typeof define === "function" && define.amd) { define("jquery", [], function () { return jQuery; }); } if (typeof noGlobal === "undefined") { window.jQuery = window.$ = jQuery; } return jQuery; } );
jQuery上的方法
- 第一部分方法,放在jQuery原型上,供实例调取使用的
- 大部分是操作DOM的
- css
- add
- remove
- toggleClass
- addClass,如$(…).addClass()
- …
- 大部分是操作DOM的
- 第二部分方法,把jQuery当做普通对象,设置的静态私有属性方法
- 大部分是工具类方法
- each
- ajax,如$.ajax()
- …
- 大部分是工具类方法
循环处理
-
JavaScript中常用的循环操作语句
- for
- while
- do-while
- 数组的迭代方法
- forEach()
- map()
- reduce()
- reduceRight()
- filter()
- find()
- findIndex()
- some()
- every()
- 对象的迭代方法
- for-in
- for-of
-
项目中选择那一种循环方案
- 首先看性能
- 好(性能高) --> 坏(性能低)
for
、while
、do-while
-->数组的迭代方法
-->for-of
-->for-in
- 数组的迭代方法内部也用的for循环,中间还有一些判断的消耗,所以比for循环要性能低一点
- 其次看各循环的作用
- for:一般指定起始和结束的条件进行循环,可用来创建指定次数的循环,或迭代
数组
/类数组
中的内容 - while:一般用于不确定具体执行次数的循环
- 基本上就是只知道循环的运行条件的那类循环
- for-of:直接迭代数据中的内容
- for-in:可以迭代对象
- for:一般指定起始和结束的条件进行循环,可用来创建指定次数的循环,或迭代
- 最后,优先使用函数式编程
- 因为可以简化操作,实现代码的低耦合高内聚
- 优先用数组的方法
- 首先看性能
-
示例
-
已知循环的具体次数
// 需求1:循环五次 // for (let i = 0; i < 5; i++) { } //命令式编程 new Array(5).fill(null).forEach(() => { //函数式编程 console.log('AA'); });
- 已知具体次数的,可以用for循环的命令式编辑方式,也可以用函数式编程
- 优先使用函数式编程,即借用数组的方式来
- 已知具体次数的,可以用for循环的命令式编辑方式,也可以用函数式编程
-
数组与伪数组的遍历迭代
// 需求2:依次迭代数组/伪数组每一项 let arr = [10, 20, 30]; arr.forEach((item, index) => { console.log(item, index); });
- 优先使用函数式编程,即借用数组的方式来
- 也可以用for循环配合length属性
- 优先使用函数式编程,即借用数组的方式来
-
要求灵活控制到具体迭代项的
// 需求3:迭代数组的奇数项-这种要求灵活控制到具体迭代项的,需要用for循环。 let arr = [10, 20, 30, 40, 50, 60, 70]; for (let i = 0; i < arr.length; i += 2) { console.log(arr[i]); } /* // 这样处理:每一项都被迭代了,只不过在奇数项才输出。不推荐使用 let arr = [10, 20, 30, 40, 50, 60, 70]; arr.forEach((item, index) => { if (index % 2 === 0) { console.log(item); } }); */
- 推荐使用for循环,能具体控制循环那些项
-
不确定具体要循环多少项
// 需求4:创建一个4位不重复的验证码 let area = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890'; //0~61 let code = ``; while (code.length < 4) { //只要验证码长度不足4位,我们就要一直处理 let ran = Math.round(Math.random() * 61), char = area.charAt(ran); if (new RegExp(char, 'i').test(code)){ continue;} code += char; } console.log(code);
- 推荐使用while循环或do-while循环
-
-
创建一个可循环特定次数的数组:new Array(数字).fill(null)
// 需求1:循环五次 // for(let i=0;i<5;i++){}//命令式编程 // new Array(5).fill(null).forEach(()=>{console.log('11111')})//函数式编程 // new Array(5)创建一个长度为5的稀疏数组,forEach等迭代方法是不支持稀疏数组的 // new Array(5).fill(null)对长度为5的稀疏数组进行填充,让其变成密集数组 // new Array(5).fill(null).forEach()对密集数组进行forEach()循环。
-
for-in循环的问题
Object.prototype.AAA = 'AAA'; let obj = { name: '挖呀挖呀挖', x: 100, 1: 20, 0: 30, [Symbol('AA')]: '哇咔咔' }; for(let key in obj){ console.log(key, obj[key]); }
-
不仅仅会迭代私有的属性,还会按照原型链去所属类的原型对象上查找。
- 一直遍历到Object.prototype为止。
- 所以for-in循环的性能很差,因为要涉及到原型链。
-
优化方式,看当前迭代到的属性是否是私有属性,若为私有,就退出循环。
for (let key in obj) { if (!obj.hasOwnProperty(key)){ break;} //此操作仅仅是不让其输出公有“可枚举”的成员,但是不能完全保证让其不迭代公有成员! console.log(key, obj[key]); }
-
- 所以for-in循环的性能很差,因为要涉及到原型链。
- 一直遍历到Object.prototype为止。
-
只能迭代
可枚举
且非Symbol类型
的成员属性。- 具有迭代限制。
-
往后想要迭代对象的成员,应尽可能的不要使用for-in循环。
-
-
对象成员的类型:字符串、Symbol类型
-
对象成员的规则
- 查看规则的方式
- Object.getOwnPropertyDescriptor(obj,key)
- Object.getOwnPropertyDescriptors(obj)
- 规则类型种类
- configurable 是否可删除
- writable 是否可修改
- enumerable 是否可枚举
- value 成员值
- 规则情况
- 默认情况下,
基于obj.xxx
或者在大括号中
设置的成员,其相应规则都是true
。 - 一般情况下,浏览器内置的成员,其规则:
enumerable=false
(不可枚举),其余的都是true
。 - 一般可以基于object.defineProperty 设置成员及其规则。
-
如果成员存在,可以为其修改规则
let obj={name:'01'} Object.defineProperty(obj,'name',{ value:'02' })//只修改obj['name']的enumerable规则为false。其余规则不变。 obj.name = '03' console.log(obj)//{name: '03'}
-
如果成员不存在,则新增一个成员,其默认规则都是false
let obj = {} Object.defineProperty(obj,'name',{ value:'02' })//设置obj['name']的value规则为'02'。其余规则都为false。 obj.name = '03' console.log(obj)//{name: '02'}
-
- 默认情况下,
- 查看规则的方式
-
常用的替代对象私有方法的方式
-
对象构造函数上的静态方法
- Object.keys(obj):获取对象
私有的
、可枚举的
、非Symbol类型的
成员 - Object.getOwnPropertyNames(obj):获取对象
私有的
、非Symbol类型的
成员「不管枚举性
」 - Object.getOwnPropertySymbols(obj):获取对象
私有的
、Symbol类型的
成员「不管枚举性
」
- Object.keys(obj):获取对象
-
获取对象所有的
私有
成员「不考虑类型和枚举的限制」let keys = Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)); console.log(keys);
- 获取对象所有的
私有
成员-简单的办法- Reflect.ownKeys(obj) ES6提供的这个API,可以直接获取对象所有的私有成员
- 获取对象所有的
-
-
迭代对象中的每一项
-
一般的需求都是只迭代私有的即可,不考虑公有的成员
// 需求5:迭代对象中的每一项「一般的需求都是只迭代私有的即可,不考虑公有的成员」 Object.prototype.AAA = 'AAA'; let obj = { name: '挖呀挖呀挖', x: 100, 1: 20, 0: 30, [Symbol('AA')]: '哇咔咔' }; let keys = Reflect.ownKeys(obj); keys.forEach(key => { console.log(key, obj[key]); });
-
this处理
-
this: 函数的执行主体。
- 通俗来讲,就是谁把这个函数执行的,谁就是this。
- 但这个不太靠谱,因为会有new及各种环境下很难确定是谁把这个函数给执行了。比如匿名函数及对象身上的方法,以及把对象身上的方法赋值给另一个变量后通过变量直接执行。
- this并不是函数执行所处的上下文。
- 执行上下文,就是函数执行时的环境,里面会包含各种变量。
- 这里主要研究的是函数中的this。
- 全局上下文中的this是window。
- 前提:是在浏览器环境下的运行。
- 通俗来讲,就是谁把这个函数执行的,谁就是this。
-
关于函数中的this到底是谁,一般只需要记住这几条规律即可
- 这个指的是原生JavaScript在不经过处理时的场景。
- 有些框架如vue会用bind把this指向改变,那在使用层面上初看时就不适用这几条规律了。不过框架中的原理还是一样的,只是可能框架会自动帮我们把一些函数的this指向给改变。
- 具体规律
-
给元素进行事件绑定,在DOM0/DOM2事件绑定时。
- 当事件触发,绑定的方法执行,方法中的this是当前被操作的元素。
- 给谁绑定的,this就是谁
document.body.onclick = function () { console.log(this); // --> document.body }; document.body.addEventListener("click", function () { console.log(this); // --> document.body });
- 当事件触发,绑定的方法执行,方法中的this是当前被操作的元素。
-
普通函数执行,看函数前面是否有
点.
-
有
点.
,点.
前面是谁,函数中的this就是谁-
例如:
Array.prototype.push()//此时push()中的this->Array.prototype [].push()//此时push()中的this->[]
-
-
没有:函数中的this是window(非严格模式)或者undefined(严格模式)
- 像
自执行函数
或回调函数
等匿名函数,如果没有经过特殊的处理,那么函数中的this
,一般都是window
/undefined
- 像
-
-
构造函数执行(new执行),
函数体中的this
指向新创建的实例对象
。 -
我们可以基于
call()
/apply()
/bind()
强制改变函数中的this的指向。- 这个是这五条规则中优先级第二高的,是在有this的情况中优先级最高的。
-
箭头函数中没有this,
所用到的this
都是其宿主环境中的
。- 宿主环境就是上级上下文。
- 这个优先级最高,无论有没有this,箭头函数内this都不是该箭头函数执行时生成的,并没有独立于
上级上下文
,都是来源于上级上下文
中的
-
- 这个指的是原生JavaScript在不经过处理时的场景。
-
箭头函数
与普通函数
- 最核心的区别:this
- 箭头函数中没有this
- 箭头函数没有arguments
- 可以基于
...剩余运算符
获取传递的实参
- 可以基于
- 箭头函数没有prototype,不能被new执行
- 语法上的区别: 箭头函数写起来更方便与更简单
- 项目中用箭头函数还是普通函数
-
不涉及到this的问题,用谁都可以
- 但推荐使用箭头函数
-
但若想作为一个构造函数,只能用普通函数
-
涉及this问题,用谁处理起来方便,就选择用谁
-
一般是外层用普通函数,内层用箭头函数
//不推荐这样在对象的方法属性上直接用箭头函数作为第一层,这样箭头函数的this就不能访问到对象自身。而是对象所在的执行上下文中的this对象。 let obj = { name: "obj", fn: () => { console.log(this,this.name); //window, 不是`obj` }, }; obj.fn();
-
比如定义对象的方法属性时,该方法属性使用普通函数或普通函数的快速写法。
let obj = { name: "obj", fn: function () { console.log(this, this.name); //obj 、 `obj` }, }; obj.fn();
let obj = { name: "obj", fn() { console.log(this, this.name); //obj 、 `obj` },//等价于 `fn: function () {console.log(this, this.name);},`; }; obj.fn();
-
-
或者是要用到当前执行上下文中的this时,优先用箭头函数
//在obj的fn的外层函数中使用普通函数或普通函数的快速写法,不过在其内部这里使用的是普通函数,导致其访问的this有问题。 let obj = { name: "obj", fn() { console.log(this, this.name); //obj 、 `obj` let self = this; // 这样在定时器的回调函数内就可以访问到fn()执行时的执行上下文中的this--一般就是obj对象自身。而不是window或undefined。 setTimeout(function () { console.log(this.name); //window.name console.log(self.name); //obj.name }, 1000); }, }; obj.fn();
//推荐在obj的fn的外层函数中使用普通函数或普通函数的快速写法,而在内部中使用箭头函数。 let obj = { name: "obj", fn() { console.log(this, this.name); //obj 、 `obj` // 这样在定时器的回调函数内就可以访问到fn()执行时的执行上下文中的this--一般就是obj对象自身。而不是window或undefined。 setTimeout(() => { console.log(this.name); //obj.name }, 1000); }, }; obj.fn();
-
-
- 最核心的区别:this
-
例子:
//------------------------------- let fang = "全局的fang"; let f = "全局中f"; let fn1 = null; const fn2 = { f2: function () { let fang = "fn2的fang"; let f = "fn2中f"; fn1 = { inner: function () { let f = "私有f"; console.log(f, fang,this); }, }; }, }; fn2.f2(); const fn3 = { f3: function () { let fang = "fn3的fang"; let f = "fn3中f"; fn1.inner(); }, }; fn3.f3(); //"私有f","fn2的fang" {inner: ƒ}
//------------------------------- let fang = "全局的fang"; let f = "全局中f"; let fn1 = null; const fn2 = { f2: function () { let fang = "fn2的fang"; let f = "fn2中f"; fn1 = { inner: function () { let f = "私有f"; console.log(f, fang,this); }, }; }, }; fn2.f2(); const fn3 = { f3: function () { let fang = "fn3的fang"; let f = "fn3中f"; const fn3 = fn1.inner fn3() }, }; fn3.f3(); //"私有f","fn2的fang" Window
//------------------------------- let fang = "全局的fang"; let f = "全局中f"; let fn1 = null; const fn2 = { f2: function () { let fang = "fn2的fang"; let f = "fn2中f"; fn1 = { inner: ()=> { let f = "私有f"; console.log(f, fang,this); }, }; }, }; fn2.f2(); const fn3 = { f3: function () { let fang = "fn3的fang"; let f = "fn3中f"; fn1.inner(); }, }; fn3.f3(); //"私有f","fn2的fang" {f2: ƒ}
//------------------------------- let fang = "全局的fang"; let f = "全局中f"; let fn1 = null; const fn2 = { f2: function () { let fang = "fn2的fang"; let f = "fn2中f"; fn1 = { inner: ()=> { let f = "私有f"; console.log(f, fang,this); }, }; }, }; fn2.f2(); const fn3 = { f3: function () { let fang = "fn3的fang"; let f = "fn3中f"; const fn3 = fn1.inner fn3() }, }; fn3.f3(); //"私有f","fn2的fang" {f2: ƒ}
例子
let obj = {
// 把自执行函数执行的返回值,赋值给 obj.fn
// obj.fn -> 0x001
fn: (function () {
return function () {
//地址:0x001
console.log(this);
};
})(),
};
obj.fn(); //this->obj
let fn = obj.fn;
fn(); //this->window
var fullName = "language"; //window.fullName=...
var obj = {
fullName: "javascript",
prop: {
getFullName: function () {
return this.fullName;
},
},
};
console.log(obj.prop.getFullName()); //this->obj.prop => obj.prop.fullName => undefined
var test = obj.prop.getFullName;
console.log(test()); //this->window => window.fullName => 'language'
var name = "window";
var Tom = {
name: "Tom",
show: function () {
console.log(this.name);
},
wait: function () {
var fun = this.show;
fun(); //this->window
},
};
Tom.wait(); // this->Tom
window.val = 1; //2 4
var json = {
val: 10, //20
dbl: function () {
this.val *= 2;
},
};
json.dbl(); //this->json => json.val *= 2
var dbl = json.dbl;
dbl(); //this->window => window.val *= 2
json.dbl.call(window); //this->window => window.val *= 2
alert(window.val + json.val); //"24"
(function () {
var val = 1; //2
var json = {
val: 10,
dbl: function () {
val *= 2;
},
};
json.dbl();
alert(json.val + val); //"12"
})();
迭代对象方法each的封装
-
检测是否是window对象
-
window上有一个window属性,指向它自身
const isWindow = function isWindow(obj) { return obj != null && obj === obj.window; };
-
-
迭代对象方法each的封装
// 笼统检测是否为对象
const isObject = function isObject(obj) {
return obj !== null && /^(object|function)$/.test(typeof obj);
};
// 检测是否是window对象
const isWindow = function isWindow(obj) {
return obj != null && obj === obj.window;
};
// 检测是否为数组或者伪数组
const isArrayLike = function isArrayLike(obj) {
if (typeof obj !== "object" || obj === null) {
return false;
}
if (Array.isArray(obj)) {
return true;
}
// 检测是否为伪数组
let length = !!obj && "length" in obj && obj.length;
if (typeof obj === "function" || isWindow(obj)) return false;
return (
length === 0 ||
(typeof length === "number" && length > 0 && length - 1 in obj)
);
};
// 迭代数组、伪数组、对象「支持中途结束循环」
const each = function each(obj, callback) {
if (typeof callback !== "function") {
callback = () => {};
}
// 让其支持“纯数字”控制循环
if (typeof obj === "number" && !isNaN(obj) && obj > 0) {
obj = new Array(obj).fill(null);
}
// 让其支持字符串
if (typeof obj === "string") obj = Object(obj);
if (!isObject(obj)) return obj;
// 迭代数组/伪数组
if (isArrayLike(obj)) {
for (let i = 0; i < obj.length; i++) {
let item = obj[i];
let res = callback.call(obj, item, i);
if (res === false) break;
}
return obj;
}
// 迭代对象
let keys = Reflect.ownKeys(obj);
for (let i = 0; i < keys.length; i++) {
let key = keys[i],
value = obj[key];
let res = callback.call(obj, value, key);
if (res === false) break;
}
return obj;
};