目录
方法三:数组方法 ==》toString() + split()
一、防抖
function debounce(fn, delay=200) {
let timeout = null; // 定时器控制
return function(...args) {
if (timeout) { // 定时器存在,表示某个动作之前触发过了
clearTimeout(timeout); // 清除定时器
timeout = null;
} else {
// 对第一次输入立即执行
timeout = setTimeout(()=>{
fn.apply(this, args); // this指向function
}, delay)
}
}
}
二、节流
function throttle(fn, time) {
let pre = 0;
let timeout = null;
return function (...args) {
const now = Date.now();
// 如果时间超过了时间间隔,立即执行函数
if (now - pre > time) {
pre = now;
fn.apply(this, args);
} else {
// 如果时间没有超过时间间隔,取消后续的定时器任务
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
// 最后一次事件的触发
timeout = setTimeout(() => {
pre = now;
fn.apply(this, args);
}, time);
}
};
}
三、instanceOf
判断数据类型的方法:typeof、 instanceOf、 Object.prototype.tostring.call()
作用:运算符,可以判断一个对象的类型
原理:原型和原型链
function myInstanceOf(obj, constructor) {
// obj=实例对象,constructor=实例对象的构造函数
let proto = obj.__proto__;
while (true) { // 遍历原型链
if (proto === null) {
return false;
}
if (proto === constructor.prototype) {
return true;
}
proto = proto.__proto__;
}
}
四、call
思路:1、判断是否是函数调用,若非函数调用抛异常;2、通过新对象(context)来调用函数:给context创建一个fn
设置为需要调用的函数,结束调用完之后删除fn
// 初级写法
Function.prototype.myCall = function (ctx, ...args) {
// ctx=上下文
// 将方法挂载到传入的上下文ctx
ctx.fn = this;
// 将挂载以后的方法调用
ctx.fn(...args);
// 将添加的属性删除
delete ctx.fn;
};
// 完整写法
Function.prototype.myCall = function (obj) {
// obj是this的指向,后面还可以有多个参数即函数本身的参数
var obj = obj || window;
obj.p = this; // 为形参定义一个方法p,并把this给这个函数
var newArguments = []; // 函数参数本身是没有this的,需要把所有参数另外保存起来,并且不保留第一个this参数,使参数回归到正常的序号
// 获取函数的参数需要用到arguments对象
for (var i = 1; i < arguments.length; i++) {
newArguments.push("arguments[" + i + "]"); // 用字符串拼接参数
}
var result = eval("obj.p(" + newArguments + ")"); // 用eval可以执行到引号里的参数
delete obj.p; // 要将p方法删除掉,因为不能改写对象
return result;
};
五、apply
思路:思路:1、判断是否是函数调用,若非函数调用抛异常;2、通过新对象(context)来调用函数:给context创建一个fn
设置为需要调用的函数,结束调用完之后删除fn
Function.prototype.myApply = function (obj, arr) {
var obj = obj || window;
obj.p = this;
if (!arr) {
// 如果执行myApply的时候没有输入参数arr,那么就直接执行方法p,不用考虑参数问题
obj.p();
} else {
// 有传入参数arr,执行就跟call一样了
var newArguments = [];
for (var i = 1; i < arguments.length; i++) {
newArguments.push("arguments[" + i + "]");
}
var result = eval("obj.p(" + newArguments + ")");
}
delete obj.p;
return result;
};
六、bind
思路:
处理边界:如果常用参数上下文context不存在的话,需要将其指向window
创造唯一的key,作为构造context内部方法名
将调用函数挂载到this指向的对象(即context)的fn属性上:
Function.prototype.myBind = function (obj) {
if (typeof this !== "function") {
throw new TypeError("wrong");
}
var that = this;
var arr = Array.prototype.slice.call(arguments, 1);
var o = function () {};
newf = function () {
var arr2 = Array.prototype.slice.call(arguments);
var arr = arr.concat(arr2);
if (this instanceof o) {
that.apply(this, arrSum);
} else {
that.apply(obj, arrSum);
}
};
o.prototype = that.prototype;
newf.prototype = new o();
return newf;
};
七、对象深拷贝
// 简易版
function deepClone(o) {
let obj = {}
for (var i in o) {
// if(o.hasOwnProperty(i)){
if (typeof o[i] === "object") {
obj[i] = deepClone(o[i])
} else {
obj[i] = o[i]
}
// }
}
return obj
}
八、手写new
new发生的事:
- 创建一个新对象
- 使新对象的__proto__指向原函数的prototype
- 改变this指向(指向新的obj)并执行该函数,执行结果保存起来作为result
- 判断执行函数的结果是不是null或Undefined,如果是则返回之前的新对象,如果不是则返回result
// 手写一个new
function myNew(fn, ...args) {
// 创建一个空对象
let obj = {}
// 使空对象的隐式原型指向原函数的显式原型
obj.__proto__ = fn.prototype
// this指向obj
let result = fn.apply(obj, args)
// 返回
return result instanceof Object ? result : obj
}
九、手写Ajax
Ajax的作用:浏览器输入网页后向服务器获取信息,当浏览器渲染网页资源后,还想获得更多的信息,页面又要重新渲染,为了页面不重新渲染
var Ajax = {
get: function (url, callback) {
// js没有与网络沟通的能力,需要创建由浏览器提供的xhr
let xhr = XMLHttpRequest();
// 定义请求
xhr.open("get", url, false) // 请求类型,URL,是否异步
// 监听readyState属性的变化
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) { // 为4则表明接收到所有相应
// 接收到所有相应并不代表成功接收了文件,文件不存在时也属于收到响应,所以需要通过状态码来判断
if (xhr.status == 200 || xhr.status == 304) {
// 如果成功接收文件,就将页面内容更改为接收到的文件的对应的内容
console.log(xhr.responseText); // 响应返回的文本
callback(xhr.responseText)
}
}
}
xhr.send() // 发送请求
},
post: function (url, data, callback) {
let xhr = new XMLHttpRequest()
// 第三个参数为是否异步执行
xhr.open('post', url, true)
// 添加http头,设置编码类型
xhr.setRequestHeader("Content-type","x-www-form-urlencoded")
xhr.onreadystatechange = function () {
if(xhr.readyState == 4) {
if(xhr.status == 200 || xhr.status == 304) {
console.log(xhr.responseText);
callback(xhr.responseText)
}
}
}
xhr.setRequestHeader('Content-type', "application/x-www-urlencoded")
xhr.send(data)
}
}
promise封装Ajax
function promiseAjax() {
let promise = new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open("get", url, true);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if ((xhr.status === 200 && xhr.status < 300) || xhr.status === 304) {
resolve(xhr.responseText);
} else {
reject(new Error(xhr.statusText));
}
}
};
xhr.send(null);
});
return promise;
}
十、数组扁平化
将多维数组转换成一维数组
方法一:ES6语法 ==》flat
语法:数组.flat(需要拉平的层数 / Infinity)
如果原数组有空位,flat()方法会跳过空位。该方法返回一个新的数组,对原数据没有影响。
let arr = [1, 2, [3, [4, 5]]];
arr.flat() // 默认拉平一层 [1, 2, 3, [4, 5]]
arr.flat(2) // [1, 2, 3, 4, 5]
不知道具体有几层,但是就想让多维数组转换成一维,可以用Infinity关键字作为参数
arr..flat(Infinity)
【拓展】数组.flatMap(需要执行的函数)
- 对原数组的每个成员执行一个函数,
相当于执行Array.prototype.map()
,然后对返回值组成的数组执行flat()
方法。该方法返回一个新数组,不改变原数组。 - flatMap()
只能展开一层数组
。
[2, 3, 4].flatMap((x) => [x, x * 2])
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
// [2, 4, 3, 6, 4, 8]
方法二:数组方法 ==》join + split
将数组转换成字符串,再将字符串切割成数组
let arr = [1, 2, [3, [4, 5]]];
arr.join(',').split(',').map(Number)
方法三:数组方法 ==》toString() + split()
return arr.toString().split(',').map(item => Number(item));
方法四:数组方法 ==》reduce
return arr.reduce((target, item) => {
return target.concat(Array.isArray(item) ? flatten(item) : item);
}, [])
方法五:ES6语法 ==》扩展运算符
while(arr.some(item => Array.isArray(item))){
arr = [].concat(...arr);
}
return arr;
方法六:递归
let res = [];
arr.forEach(item => {
if (Array.isArray(item)) {
res = res.concat(flatten(item))
} else {
res.push(item);
}
});
return res;
十一、手写promise(A+版)
思路:
- 初始化:包括3个状态、2个方法、1个then
- this指向:因为实例化问题,所以要用bind修改一下this指向,否则拿不到value
- 状态跳转:当状态为pending时可以跳转到resolve | reject,但是resolve | reject不能跳回pending,而且resolve和reject之间不能相互跳转
- then的功能:有2个参数,分别是2个方法,如果resolve就将value传给onFullfilled方法,如果reject就将reason传给onReject方法,如果是pending状态,
- 完善功能:①promise的异步:利用setTimeout;②promise的多重then回调:then直接return本promise,就可以链式拿到value;③promise传入的参数不是函数时:加入if判断传入的参数是否是函数,如果是函数就不用管,如果不是则将参数强行转换成空函数,这样控制台就不会报错了;④promise里参数是error:用try...catch捕获error给reject
无注释的代码:
class myPromise {
static PENDING = "pending";
static FULLFILLED = "fullfilled";
static REJECTED = "rejected";
constructor(executor) {
this.status = myPromise.PENDING;
this.value = null;
this.resolveCallbacks = [];
this.rejectCallbacks = [];
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
resolve(value) {
if (this.status === myPromise.PENDING) {
this.status = myPromise.FULLFILLED;
this.value = value;
setTimeout(() => {
this.resolveCallbacks.map((callback) => {
callback.onFullfilled(this.value);
});
});
}
}
reject(reason) {
if (this.status === myPromise.PENDING) {
this.status = myPromise.REJECTED;
this.value = reason;
setTimeout(() => {
this.rejectCallbacks.map((callback) => {
callback.onFullfilled(this.value);
});
});
}
}
then(onFullfilled, onRejected) {
if (typeof onFullfilled !== "function") {
onFullfilled = () => this.value;
}
if (typeof onRejected !== "function") {
onRejected = () => this.value;
}
return new myPromise((mResolve, mReject) => {
if (this.status === myPromise.PENDING) {
this.callbacks.push({
onFullfilled: (value) => {
let result = onFullfilled(this.value);
mResolve(result);
},
onRejected: (reason) => {
let result = onRejected(this.value);
mReject(result);
},
});
}
if (this.status === myPromise.FULLFILLED) {
setTimeout(() => {
let result = onFullfilled(this.value);
mResolve(result);
});
}
if (this.status === myPromise.REJECTED) {
setTimeout(() => {
let result = onRejected(this.value);
mReject(result);
});
}
});
}
}
带有完整注释的代码:
// 自定义promise库
// 因为原生promise对象是new Promise((resolve, reject)->{}),可以用class类来写
class myPromise {
// promise有3个状态:pending,fullfilled,rejected
// 将promise的3个状态定义成静态属性
static PENDING = "pending"; // 开始状态
static FULLFILLED = "fullfilled"; // 成功状态
static REJECTED = "rejected"; // 失败状态
// 参数:executor执行者
constructor(executor) {
// promise有2个属性,将状态属性定义成静态的
this.status = myPromise.PENDING; // 刚开始promise的状态是开始状态pending
this.value = null; //promise的返回值,设为空
// 构造缓存队列,用于存储onFullfilled, onRejected
this.resolveCallbacks = [];
this.rejectCallbacks = [];
// 如果promise里丢出一个error,就将error给reject,没有error就按计划执行resolve、reject
try {
// 用executor调用方法
executor(this.resolve.bind(this), this.reject.bind(this)); // es6方法是严格模式,通过this是访问不到的,所以要用bind来控制this指向this。也就是让resolve、reject在class里实例化一下
} catch (error) {
this.reject(error); // 这里不需要用bind,因为是直接执行,而不是创建实例后执行
}
}
// promise有3个方法
// 执行回调方法(resolve, reject) => {}
// 成功的回调,作用:1.通知promise修改状态:pending->fullfilled;2.返回值value
resolve(value) {
// 参数:希望返回的值value
// 状态从pending更改到fullfilled或rejected后,是不能再改动的
// 所以需要判断,当状态为pending时才可以发生状态改变,否则不能改变状态
if (this.status === myPromise.PENDING) {
this.status = myPromise.FULLFILLED;
this.value = value;
// promise里有异步任务resolve和同步任务时,将他们区分开成为异步任务,需要用setTimeout,否则在promise里都是自上而下执行的同步任务
setTimeout(() => {
this.resolveCallbacks.map((callback) => {
// 遍历所有callback,将resolve的返回值给onFullfilled
callback.onFullfilled(this.value);
});
});
}
}
// 失败的回调,作用:1.通知promise修改状态:fullfilled->rejected;2.返回当前失败的原因
reject(reason) {
// 参数:失败的原因reason
// 状态从pending更改到fullfilled或rejected后,是不能再改动的
// 所以需要判断,当状态为pending时才可以发生状态改变,否则不能改变状态
if (this.status === myPromise.PENDING) {
this.status = myPromise.REJECTED;
this.value = reason;
// promise里有异步任务resolve和同步任务时,将他们区分开成为异步任务,需要用setTimeout,否则在promise里都是自上而下执行的同步任务
setTimeout(() => {
this.rejectCallbacks.map((callback) => {
// 遍历所有callback,将resolve的返回值给onFullfilled
callback.onFullfilled(this.value);
});
});
}
}
// promise.then()是微任务
then(onFullfilled, onRejected) {
// 固定参数,是2个方法。原生promise里就是2个方法,一个是成功,一个是失败
// 当resolve方法调用时,会将返回值传给onFullfilled
// 当reject方法调用时,会将返回值传给onRejected
// 原生promise里规定,如果传入的2个参数不是函数,就要被忽略
// then里可以传null或者为空 =》promise.then(null) 或者 promise.then()
// 为了使我们的方法也能做到这一点,需要将传入的参数(传入的参数不是函数)强行转换为函数,这样就不会报错了
if (typeof onFullfilled !== "function") {
// onFullfilled = () => {};
onFullfilled = () => this.value;
}
if (typeof onRejected !== "function") {
// onRejected = () => {};
onRejected = () => this.value;
}
// 当promise.then().then(...)时,中间的.then().是不会影响value的传递的,所以要解决then的穿透问题。所以then要重新返回一个新的promise去拿到value
return new myPromise((mResolve, mReject) => {
if (this.status === myPromise.PENDING) {
// 缓存onFullfilled, onRejected,当setTimeout到时间时调用resolve时再执行缓存的方法
this.callbacks.push({
// 为了完成promise的链式调用
onFullfilled: (value) => {
let result = onFullfilled(this.value);
mResolve(result);
},
onRejected: (reason) => {
let result = onRejected(this.value);
mReject(result);
},
});
}
// 当状态是fullfilled时,调用onFullfilled方法,将resolve的返回值传给onFullfilled方法
if (this.status === myPromise.FULLFILLED) {
// 由于promise.then()是微任务,如果只写onFullfilled(this.value)就不是微任务,程序没有异步,而是从上至下执行了,所以需要进行异步操作
setTimeout(() => {
// onFullfilled(this.value);
let result = onFullfilled(this.value);
mResolve(result);
});
}
// 当状态是rejected时,调用onRejected方法,将reject的返回值传给onRejected方法
if (this.status === myPromise.REJECTED) {
// 由于promise.then()是微任务,如果只写onRejected(this.value)就不是微任务,程序没有异步,而是从上至下执行了,所以需要进行异步操作
setTimeout(() => {
// onRejected(this.value);
let result = onRejected(this.value);
mReject(result);
});
}
});
}
}
Promise.all(arr)
、、、
// all是静态方法,接收一个参数:数组arr,返回一个promise对象
static all(arr) {
let result = []; // 结果数组,用来存放输出结果
let index = 0; // 目的是判断是否遍历完所有的arr元素,因为arr里有promise对象时,是会出现异步操作的,但是循环arr是瞬间完成的,当循环完成后调用resolve时,不一定循环所有的元素,比如有些promise对象因为异步就没被循环到
return myPromise((resolve, reject) => {
// 定义一个方法,作用:将arr循环的结果放入result数组中
function addData(key, value) {
result[key] = value;
index++; // 每放一个结果进result,index自加1
// 判断index是否等于arr的长度,等于则表明已经循环了所有元素并且循环完毕了
if (index === arr.length) {
// 循环结束后调用resolve方法,将结果数组result返回
resolve(result); // 因为resolve,所以这个方法得放进myPromise里
}
}
// 循环传入的数组arr,如果arr里的元素是普通值(如:字符串),就直接放进result中,如果是promise对象,则先执行该promise对象,再将promise对象的输出放入result数组里
for (let i = 0; i < arr.length; i++) {
// 判断当前元素是否myPromise下的一个实例,如果是,则该元素是一个promise对象,如果不是,则该元素是一个普通值
if (arr[i] instanceof myPromise) {
// promise对象
// 拿到当前元素,执行其then方法,传递成功回调和失败回调,如果成功,则将value传给result,如果失败则直接调用reject()并传入失败原因reason
arr[i].then(
(value) => {
addData(i, value);
},
(reason) => {
reject(reason);
}
);
} else {
// 普通值
addData(i, arr[i]); // 将普通值放入result数组中
}
}
// 循环结束后调用resolve方法,将结果数组result返回
// resolve(result);
});
}
}
Promise.race(arr)
静态方法,需要一个数组arr作为参数,返回值是一个promise。promise的状态和结果,由arr中最快得到结果决定。arr中的普通值被视为成功的promise
static race(arr) {
return new myPromise((resolve, reject) => {
// 遍历,看看谁最快
arr.forEach((item) => {
if (item instanceof myPromise) {
// 如果该元素是promise对象,返回结果就是该promise的结果
item.then(resolve, reject);
} else {
// 如果该元素是普通值,返回的是成功的promise
// resolve(item); // 不能直接这么写,因为元素如果是promise对象的话,输出返回值还包裹了一层then,就是异步,所以元素为普通值时也需要用一个异步包裹一下
queueMicrotask(() => { // queueMicrotask执行微任务
resolve(item);
});
}
});
});
}
Promise.resolve(value)
作用:将给定的值转换成promise对象,resolve方法的返回值就是promise对象
在resolve方法内部,会创建一个promise对象,并将传给resolve方法的值包裹在这个promise对象里,并将创建出来的promise对象作为resolve返回值,正因为这样,才能在resolve方法后链式调用then方法,通过then方法的成功回调,拿到传给resolve方法的值
如果传给resolve方法的是一个promise对象,则将这个promise对象原封不动的作为链式调用then的成功回调,返回这个promise的值
、、、
static resolve(value) {
// 判断value是否是myPromise对象的一个实例,如果是则value是promise对象,如果不是则value是一个普通值
if (value instanceof myPromise) {
// 是promise对象,直接返回
return value;
} else {
// 是普通值,创建promise对象,传递一个执行器,在执行器中拿到resolve方法,利用resolve方法将value返回
return new myPromise((resolve) => {
resolve(value);
});
}
}
Promise.reject()
静态方法,返回一个promise对象,不管传入的值是什么,都会被包裹成为失败的promise对象。
static reject(value) {
return new Promise((resolve, reject) => {
reject(value); // 将传入的value作为失败的原因reason
});
}
Promise.finally(callback)
特点:
- 无论promise对象最终的状态是成功还是失败,finally这个方法都会被调用一次
- 在finally方法后面,可以链式调用then方法,拿到当前这个promise对象最终返回的结果
finally方法不是静态方法,需要被定义在myPromise这类的原型对象上
static resolve(value) {
// 判断value是否是myPromise对象的一个实例,如果是则value是promise对象,如果不是则value是一个普通值
if (value instanceof myPromise) {
// 是promise对象,直接返回
return value;
} else {
// 是普通值,创建promise对象,传递一个执行器,在执行器中拿到resolve方法,利用resolve方法将value返回
return new myPromise((resolve) => {
resolve(value);
});
}
}
// finally方法不是静态方法,需要被定义在myPromise这类的原型对象上
//1、 参数为回调函数callback,因为无论当前promise对象是成功或失败都要去调用这个回调函数
finally(callback) {
// 调用then方法去得知当前promise对象是成功还是失败
// 在成功与失败回调里都调用callback
// 2、在finally方法后面可以链式调用then方法,得到当前promise状态
// 链式调用then方法,finally方法最终也是要返回一个promise对象,而在finally内部调用了then,then返回的就是promise对象,所以可以将内部调用的then返回return出去
return this.then(
(value) => {
// finally方法中是可以return一个promise对象的
// 如果return了一个promise对象,需要等promise对象执行完毕了再return value,这是一个异步操作,而不是马上return value
return myPromise.resolve(callback()).then(() => value); // 不管传入的是普通值,还是promise对象,都将其转换成promise对象
// callback();
// return value; // return这个value,下一个then方法才能拿到这个value
},
(reason) => {
return myPromise.resolve(callback()).then(() => {
throw reason;
});
// callback();
// throw reason; // 失败状态的原因reason用throw传递出去
}
);
}
Promise.catch()
作用:处理当前这个promise对象最终状态为失败的情况,意思是当调用then方法时,可以不传递失败回调,如果不传递失败回调,这个失败回调就会被catch方法捕获,从而去执行catch方法中的回调函数
实现:在catch方法内部调用then方法,在成功的回调undefined,在失败的回调传递一个回调函数
原型方法
catch(failCallback) {
// 为了使catch方法后面可以链式调用then方法,需要将其return出去
return this.then(undefined, failCallback);
}
十二、函数柯里化
应用一:参数复用;
应用二:兼容性检测:有的函数和方法是不能被浏览器支持的,如事件监听,去判断这个方法是否被浏览器支持;
应用三:延迟执行
// 应用:延迟执行
function add() {
// 因为传入的参数数量不确定,这里就不设置参数了
let args = Array.prototype.slice.call(arguments); // 把保存参数的arguments赋给args,但是arguments是对象不是数组,不能用数组的方法,所以要将对象转化成数组
let inner = function () {
// 内部函数,实际是接收第二次传入的参数,即add(1)(2)(3)...的第2个括号
args.push(...arguments); // 将第2个括号的参数加入到第1个括号的参数内
return inner;
}; // 递归,就可以多次调用自己
inner.toString = function () {
return args.reduce(function (prev, cur) {
return prev + cur;
});
};
return inner; // 返回内部函数
}
十三、setTimeout实现setInterval
function myInterval(func, time) {
//func=函数,time=延迟时间
function inside() {
func();
setTimeout(inside, time);
}
setTimeout(inside, time);
}
清除setTimeout实现的setInterval
// 定义一个全局变量timer
timer = null;
function mockSetInterval(fn, delay) {
const inside = () => {
fn();
timer = setTimeout(inside, delay);
};
timer = setTimeout(inside, delay);
}
//清除定时器
timer && clearTimeout(timer);
十四、JS实现拖拽
并不是所有元素都可以拖拽,但是图片是可拖拽的
应用:验证码登录验证
方式一:html5新增的draggable属性打开(draggable = "true"),再用js进行操作(document.addEventListener("拖拽事件名", (e) => {事件的具体操作}, false))
方式二: 鼠标事件,将拖拽分为三步:鼠标按下mousedown、鼠标移动mousemove、鼠标松开mouseup
十五、手写数组转树
function transTree(data) {
let result = []
let map = {}
if (!Array.isArray(data)) {//验证data是不是数组类型
return []
}
data.forEach(item => {//建立每个数组元素id和该对象的关系
map[item.id] = item //这里可以理解为浅拷贝,共享引用
})
data.forEach(item => {
let parent = map[item.parentId] //找到data中每一项item的爸爸
if (parent) {//说明元素有爸爸,把元素放在爸爸的children下面
(parent.children || (parent.children = [])).push(item)
} else {//说明元素没有爸爸,是根节点,把节点push到最终结果中
result.push(item) //item是对象的引用
}
})
return result //数组里的对象和data是共享的
}
console.log(JSON.stringify(transTree(data)))
十六、获取url所有参数
思路:
- 获取url参数部分
- 声明一个保存参数的对象
- 将参数分割成数组保存
- 遍历每对参数,再获取键值对,存储到保存参数的对象里
- 注意!对中文解码
function getURLParams() {
// 1、获取url参数部分
// window.location.search 是 JavaScript 中的一个属性,它表示当前页面的 URL 中的查询字符串部分
let params = location.search.length > 0 ? location.search.substring(1) : ""; // 看url是否存在,如果存在就切割?后面的部分,如果不存在就置空
// 2、声明一个保存参数的对象
let obj = {};
// 3、将每对参数进行分割成数组保存
let items = params.length ? params.split("&") : []; // 参数都是以&拼接的,所以分割的时候以&分割,如果没有参数items就是空数组
let item = "",
key = "",
value = ""; // 定义每个参数、键值
let len = items.length;
// 4、遍历每对参数,取键值对,保存在obj里,注意对中文参数解码decodeURIComponent
for (let i = 0; i < len; i++) {
item = items[i].split("=");
key = decodeURIComponent(item[0]);
value = decodeURIComponent(item[1]);
// 保存到obj,有键值对才需要保存
if (key.length) {
obj[key] = value;
}
}
return obj;
}
测试:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="./10-获取所有url参数.js"></script>
</head>
<body>
<script>
console.log(getURLParams());
</script>
</body>
</html>
十七、手写一个发布-订阅模式
发布-订阅模式是什么 =》
思路:
- 声明一个事件变量对象存储消息事件
- 定义添加方法,将事件添加到事件变量
- 定义删除方法,将事件从事件变量里删除
- 定义派发方法,调用时间变量中的事件
基本框架:
class EventSubscription {
constructor() {
// 1、定义事件变量对象
this.eventObject = {};
}
// 2、定义事件添加方法
addEventFun() {}
// 3、定义事件删除方法
deleteEventFun() {}
// 4、定义事件派发方法
emitEventFun() {}
}
方法的具体实现:
class EventSubscription {
constructor() {
// 1、定义事件变量对象
this.eventObject = {};
}
// 2、定义事件添加方法
// 添加消息事件是需要一个事件名的,以及消息事件的事件回调函数
// 需要将消息事件添加到事件变量中,并且不止一个变量
// 添加事件变量时需要判断是否存在当前事件,做不同的处理
addEventFun(name, callback) {
// 判断事件变量中是否有当前事件, 如果没有的话初始化一个空数组
if (!this.eventVarObject[name]) {
this.eventVarObject[name] = [];
}
// 如果存在,那就是说明该事件多个订阅者,继续往后push
this.eventVarObject[name].push(callback);
}
// 3、定义事件删除方法
// 删除消息事件是需要一个事件名的,以及消息事件的事件回调函数
// 事件变量中,对应的事件回调函数不止一个,需要对符合条件的删除,仅删除这个callback
// 如果事件回调函数不存在的话,直接删除事件
deleteEventFun() {
// 事件回调函数不存在,删除相应整个事件
if (!callback) {
delete this.eventVarObject[name];
return;
}
// 需要对符合条件的删除
const index = this.eventVarObject[name].findIndex(
(item) => item == callback
);
this.eventVarObject[name].splice(index, 1);
}
// 4、定义事件派发方法
// 事件派发方法只需要事件名,确定是哪个事件,也可以传递参数
// 将事件变量中,对应的事件回调函数依次执行
emitEventFun() {
// 确认事件是否订阅
if (!this.eventVarObject[name]) return;
// 依次执行事件回调函数
this.eventVarObject[name].forEach((callback) => {
callback(...data);
});
}
}
十八、Proxy 实现观察者模式
观察者模式(Observer mode):指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行
思路:实现observable
和observe
这两个函数,observable
函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数
// 观察者函数都放进Set里
const queuedObservers = new Set();
// 观察者函数
const observe = fn => queuedObservers.add(fn);
// 返回原始对象的代理
const observable = obj => new Proxy(obj, {set});
// 拦截函数,自动执行所有的观察者
function set(target, key, value, receiver) {
// 设置target对象的key属性等于value
const result = Reflect.set(target, key, value, receiver);
// 自动执行所有观察者函数
queuedObservers.forEach(observer => observer());
return result;
}
十九、实现箭头函数的this指向
function func1() {
// this.val1的this指向func1
() => {
this.val1;
};
const that = this; // 这里that和箭头函数同级,拿到的this都是func1的this
function func2() {
// 要想在func2内部拿到箭头函数里的val1,如何实现?
that.val1; // 就可以通过that拿到箭头函数里的val1
}
}
二十、修改this指向
封装函数 f,使 f 的 this 指向指定的对象
function bindThis(f, oTarget) {
return function() {
return f.apply(oTarget, arguments)
}
}
function bindThis(f, oTarget) {
return function() {
return f.call(oTarget, ...arguments)
}
}
function bindThis(f, oTarget) {
return f.bind(oTarget)
}