JS原理类
Ajax的请求过程(手写ajax)
使用readystatechange 事件监控 readyState 的值
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("get", "example.php", true);
xhr.send(null);
使用onload事件监控 readyState 的值
let xhr = new XMLHttpRequest();
xhr.onload = function() {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
call/bind/apply
规定this的指向:apply(), call(),bind()
Function.prototype._call = function(thisArg, ...args) {
//1.首先接收对象
//如果是undefined或者 null 指向window,否则使用 Object() 将上下文包装成对象
const context = Object(thisArg) || window;
//2.然后在对象里新建一个临时属性接收this
// 通过 obj.fn() 执行时,this 会指向前面的 obj 对象,所以this是,this指向函数foo
// 为context设置一个临时属性接收指向函数foo的this指针
//属性可以用symbol,避免属性名冲突
const key = Symbol()
context[key] = this
//3.执行对象内的函数
const result = context[key](...args)
//4.删除临时对象
delete context[key]
//5.返回执行结果
return result
}
// 只需要把第二个参数改成数组形式就可以了。
Function.prototype._apply = function(thisArg, array = []) {
const context = Object(thisArg) || window;
//给context新增一个独一无二的属性以免覆盖原有属性
const key = Symbol()
context[key] = this
const result = context[key](...array)
delete context[key]
return result
}
Function.prototype._bind = function(ctx, ...args) {
// 下面的this指向调用_bind的函数,保存给_self
const _self = this
// bind 要返回一个函数, 就不会立即执行了
const newFn = function(...rest) {
// 调用 call 修改 this 指向
return _self.call(ctx, ...args, ...rest)
}
if (_self.prototype) {
// 复制源函数的prototype给newFn 一些情况下函数没有prototype,比如箭头函数
newFn.prototype = Object.create(_self.prototype);
}
return newFn
}
书写注意点: mycall:参数一个obj,一个args,contex是Object(obj) || window,Symbol不用new
myapply:传入数组,测试集要用数组
mybind,返回函数用call,因为参数是...args,...rest
Promise.all
/*
注意点:
1.函数返回的是一个Promise对象
2.最好判断一下传入的参数是否为数组
3.并不是push进result数组的,而是通过下标的方式进行存储,这是因为我们为了保证输出的顺序,因为Promise对象执行的时间可能不同,push的话会破坏顺序。
4.通过计数标志来判断是否所有的promise对象都执行完毕了,因为在then中表示该promise对象已经执行完毕。
*/
function PromiseAll(promiseArray) { //返回一个Promise对象
return new Promise((resolve, reject) => {
if (!Array.isArray(promiseArray)) { //传入的参数是否为数组
return reject(new Error('传入的参数不是数组!'))
}
const res = []
let counter = 0 //设置一个计数器
for (let i = 0; i < promiseArray.length; i++) {
Promise.resolve(promiseArray[i]).then(value => {
counter++ //使用计数器返回 必须使用counter
res[i] = value
if (counter === promiseArray.length) {
resolve(res)
}
}).catch(e => reject(e))
}
})
}
instanceof
function myInstanceof(obj, func) {
if(!['function', 'object'].includes(typeof obj) || obj === null) {
// 基本数据类型直接返回false,因为不满足instanceof的左侧参数是对象或者说引用类型
return false
}
let objProto = obj.__proto__, funcProto = func.prototype
while(objProto !== funcProto) {
// obj.__proto__不等于func.prototype时,继续通过__proto__向上层查找
// 当找到原型链尽头Object.prototype.__proto__=null 时还未找到,就返回false
objProto = objProto.__proto__
if(objProto === null){
return false
}
}
// obj.__proto__ 等于 prototype = func.prototype 时,不会进入上面循环,返回true
// 不等进入上面循环,找到相等时会跳出循环,走到这里返回true
return true
}
//测试
function A(){}
let a=new A;
console.log(myInstanceof(a,A))//true
console.log(myInstanceof([],A))//false
防抖和节流
函数防抖和节流,都是控制事件触发频率的方法。
防抖(debounce):
防抖触发高频率事件时n秒后只会执行一次,如果n秒内再次触发,则会重新计算。
简单概括:每次触发时都会取消之前的延时调用。
场合具体有:
- search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
- window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
使用示例:
function handleInput() {
console.log('输入已停止');
}
const input = document.querySelector('#input');
input.addEventListener('input', debounce(handleInput, 1000));
节流(thorttle):
高频事件触发,每次触发事件时设置一个延迟调用方法,并且取消之前延时调用的方法。
简单概括:每次触发事件时都会判断是否等待执行的延时函数。
场合具体有:
- 鼠标不断点击触发,mousedown(单位时间内只触发一次)
- 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
function throttle(fn, delay) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
}
使用示例:
function handleResize() {
console.log('窗口大小已更改');
}
window.addEventListener('resize', throttle(handleResize, 1000));
细节问题:为什么节流用timer=null而防抖用clearTimeout(timer)?
times = null 只是将定时器的指向改为null,并没有在内存中清除定时器,定时器还是会如期运行;如同在debounce函数中将times = null并不能达到防抖的目的,因为每个定时器都只是将内存地址指向了null,而每个定时器都将会执行一遍.
而clearTimeout(times)会将定时器从内存中清除掉.
另外关于定时器是否需要用完清除的问题.
具体还得看需求,如果是很少个数的定时器,可以不清除;如果数量很多或者数量不可控,则必须要做到手动清除,否则定时器将会非常占用电脑cpu.非常影响性能.
深拷贝与浅拷贝
new
// 首先创建一个新对象,这个新对象的__proto__属性指向构造函数的prototype属性
// 此时构造函数执行环境的this指向这个新对象
// 执行构造函数中的代码,一般是通过this给新对象添加新的成员属性或方法。
// 最后返回这个新对象。
// func是构造函数,...args是需要传给构造函数的参数
function myNew(func, ...args) {
// 生成原型链
var obj = Object.create(func.prototype);
// 盗用构造函数继承
func.call(obj, ...args);
// 最后return这个对象
return obj;
}
测试:
function Person(name, age) {
this.name = name;
this.age = age;
}
const person = myNew(Person, 'John', 25);
console.log(person.name); // 'John'
console.log(person.age); // 25
简单数据结构类
数组排序
js中的sort()方法用于对数组元素进行排序,具体是如何实现的?查阅资料发现,V8 引擎 sort 函数只给出了两种排序 InsertionSort 和 QuickSort,数组长度小于等于 22 的用插入排序 InsertionSort,比22大的数组则使用快速排序 QuickSort。源码中这样写道:
// In-place QuickSort algorithm.
// For short (length <= 22) arrays, insertion sort is used for efficienc
Array.sort()的快排实现方式
function quickSort(arr = []) {
if (arr.length <= 1) {
return arr;
}
const pivot = arr[Math.floor(arr.length / 2)];
const left = [];
const right = [];
for (let i = 0; i < arr.length; i++) {
if (i === Math.floor(arr.length / 2)) {
continue;
}
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
Array.sort()的插入排序实现方式
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
let temp = arr[i];
let j = i - 1;
while (j >= 0 && arr[j] > temp) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = temp;
}
return arr;
}
初始状态:[5, 2, 6, 1, 3, 9, 8, 7, 4]
第一步:[2, 5, 6, 1, 3, 9, 8, 7, 4]
第二步:[2, 5, 6, 1, 3, 9, 8, 7, 4]
第三步:[1, 2, 5, 6, 3, 9, 8, 7, 4]
第四步:[1, 2, 3, 5, 6, 9, 8, 7, 4]
第五步:[1, 2, 3, 5, 6, 9, 8, 7, 4]
第六步:[1, 2, 3, 5, 6, 8, 9, 7, 4]
第七步:[1, 2, 3, 5, 6, 7, 8, 9, 4]
第八步:[1, 2, 3, 5, 6, 7, 8, 9, 4]
第九步:[1, 2, 3, 4, 5, 6, 7, 8, 9]
数组去重
- 利用Set数据结构去重
const arr = [1, 2, 2, 3, 3, 3];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1, 2, 3]
- 利用Array.prototype.filter()方法去重
const arr = [1, 2, 2, 3, 3, 3];
const uniqueArr = arr.filter((item, index, arr) => arr.indexOf(item) === index);
console.log(uniqueArr); // [1, 2, 3]
- 利用Array.prototype.reduce()方法去重
const arr = [1, 2, 2, 3, 3, 3];
const uniqueArr = arr.reduce((prev, curr) => prev.includes(curr) ? prev : [...prev, curr], []);
console.log(uniqueArr); // [1, 2, 3]
- 利用Object对象去重
const arr = [1, 2, 2, 3, 3, 3];
const obj = {};
const uniqueArr = arr.filter(item => obj.hasOwnProperty(item) ? false : (obj[item] = true));
console.log(uniqueArr); // [1, 2, 3]
数组扁平化
- flat(depth)
let a = [1,[2,3,[4,[5]]]];
a.flat(Infinity); // [1,2,3,4,5] a是4维数组
- for循环
var arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]];
function flatten(arr) {
var res = [];
for (let i = 0, length = arr.length; i < length; i++) {
if (Array.isArray(arr[i])) {
res = res.concat(flatten(arr[i])); //concat 并不会改变原数组
//res.push(...flatten(arr[i])); //扩展运算符
} else {
res.push(arr[i]);
}
}
return res;
}
flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
- while循环
var arr1 = [1, 2, [3], [1, 2, 3, [4, [2, 3, 4]]]];
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
//arr = Array.prototype.concat.apply([],arr);
}
return arr;
}
flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
- reduce方法
var arr1 = [1, 2, [3], [1, 2, 3, [4, [2, 3, 4]]]];
function flatten(arr) {
return arr.reduce((res,next) =>{
return res.concat(Array.isArray(next)? flatten(next) : next);
},[]);
}
- stack方法
var arr1 = [1, 2, [3], [1, 2, 3, [4, [2, 3, 4]]]];
function flatten(input) {
const stack = [...input]; //保证不会破坏原数组
const result = [];
while (stack.length) {
const first = stack.shift();
if (Array.isArray(first)) {
stack.unshift(...first);
} else {
result.push(first);
}
}
return result;
}
flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
通用计数器组件
通用计数器组件是一种在前端开发中常见的组件,它提供了计数器的基本功能,包括增加、减少、获取当前值等操作,并且可以通过事件回调的方式来监听计数器值的变化。通用计数器组件可以被广泛应用于各种场景中,例如商品数量的选择、投票统计等等。由于其常用性和通用性,因此在前端开发中经常需要使用通用计数器组件。
// 定义一个构造函数 Counter
function Counter(initialValue = 0) {
// 初始化计数器的值和回调函数列表
this.value = initialValue;
this.listeners = [];
}
// 定义 Counter 的原型对象上的 increment 方法
Counter.prototype.increment = function() {
// 增加计数器的值
this.value++;
// 通知所有回调函数,计数器的值已经发生了变化
this.notifyListeners();
};
// 定义 Counter 的原型对象上的 decrement 方法
Counter.prototype.decrement = function() {
// 减少计数器的值
this.value--;
// 通知所有回调函数,计数器的值已经发生了变化
this.notifyListeners();
};
// 定义 Counter 的原型对象上的 getValue 方法
Counter.prototype.getValue = function() {
// 获取当前计数器的值
return this.value;
};
// 定义 Counter 的原型对象上的 setValue 方法
Counter.prototype.setValue = function(newValue) {
// 设置计数器的值为 newValue
this.value = newValue;
// 通知所有回调函数,计数器的值已经发生了变化
this.notifyListeners();
};
// 定义 Counter 的原型对象上的 addListener 方法
Counter.prototype.addListener = function(callback) {
// 将回调函数添加到回调函数列表中
this.listeners.push(callback);
};
// 定义 Counter 的原型对象上的 removeListener 方法
Counter.prototype.removeListener = function(callback) {
// 查找回调函数在回调函数列表中的索引
const index = this.listeners.indexOf(callback);
// 如果找到了回调函数,则将其从回调函数列表中移除
if (index !== -1) {
this.listeners.splice(index, 1);
}
};
// 定义 Counter 的原型对象上的 notifyListeners 方法
Counter.prototype.notifyListeners = function() {
// 遍历回调函数列表,依次调用每个回调函数并传入计数器的当前值
for (const listener of this.listeners) {
listener(this.value);
}
};