手写 bind 函数
知识点:
this 指向(作用域), this的指向是在调用时确定的, 由调用当前方法的对象决定;
不同于自由变量,自由变量的作用域决定于 其本身定义的位置。
分析:bind 函数的特点
1. 改变 this 指向
2. 参数以数组形式表示
3. 返回一个函数
Function.prototype.bind1 = function (self, ...rest) {
const _self = this;
return function() {
_self.apply(self, [...rest]);
}
}
手写深拷贝
知识点:
引用类型和值类型的在内存中的存储方式
引用类型:在栈内存中存储变量名及其值对应的指针,在堆内存中存储其值
深拷贝的目的是新建一个堆内存用来存储变量,且不改变原来的堆内存和指针;
考虑到深拷贝针对非空对象,书写时要注意一下几点
1. 引用类型和值类型的判断
2. null 的 判断
3. 数组 和 对象的区分
4. 排除 原型链 上的属性
5. 递归
function deepCopy(obj) {
if (typeof obj !== 'object' || obj == null) {
return obj;
}
let result;
if (obj instanceof Array) {
result = [];
} else {
result = {};
}
for (key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepCopy(obj[key])
}
}
return result;
}
手写防抖函数
1. 防抖是指,在短时间(自定义)多次触发该函数时,只执行最后一次。
可以与公交车和上车乘客作比较:假如公交车到站后,每上来一个乘客,公交车司机就踩一下油门,踩一脚刹车;
假如有多个乘客,就会出现,公交车走,停,走,停,哎走,哎停……像是在抖动。
为了防止这种抖动的情况,公交车司机要等乘客上完之后再踩油门。
如果乘客没有上完,那就有停止之前准备踩油门的动作。
关键点:
1. 准备踩油门 -- 定时器
2. 后面还有乘客要上车,停止准备踩油门的动作 -- 清除定时器
3. 乘客全部上车后,踩油门 -- 执行预定的函数
function debounce(fn, delay) {
let timer = null;
return function() {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, delay);
}
}
手写节流函数
1. 节流是指,短时间只执行第一次。
2. 关键点:
a. 定时器,定时器的存在代表将会执行一次目标函数
b. 如果定时器存在,后续的触发不应做出任何动作
c. 指定时间段
d. 在执行过目标函数后,清除定时器
function throttle(fn, delay) {
let timer;
if (!timer) {
setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, delay);
}
}
手写 简易的ajax请求
1. 初始化xhr实例
2. 激活实例(open)
3. 监听readystate (onreadystatechange)
4. 检查 redaystate 和 status 的值
5. 发送该请求;
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/path', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
alert(xhr.responseText);
} else {
alert('error');
}
}
}
xhr.send();
手写 简易ajax请求(promise)
function ajax(path, method = 'GET', data = null) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method,path, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(xhr.responseText);
}
}
xhr.send(data);
});
return p;
}
try {
const res = async ajax('/api/path', 'POST', {id: 1, exp: 2})
} catch(e) {
console.log(e)
}
手写 通用的事件监听函数
1. 事件绑定的dom节点
2. 事件类型
3. 事件触发后要执行的方法
4. 点击特定元素才触发事件(可选, 一切css支持的选择器,但是要注意 matchs 方法本身的兼容性问题)
function bindEvent(elem, type, fn, selector = null) {
elem.addEventListener(type, event => {
const target = event.target;
if (selector) {
if (target.matchs(selector)) {
fn.call(target, event);
}
} else {
fn.call(target, event);
}
})
}
手写 深度比较
const obj1 = {a: 100, b: {x: 100, y:200}}
const obj2 = {a: 100, b: {x: 100, y:200}}
console.log(obj1 === obj2)
function isObject() {
return typeof obj === 'object' && obj !== null
}
function isEqual(obj1, obj2) {
if (!isObject(obj1) || !isObject(obj2)) {
return obj1 === obj2;
}
if (obj1 === obj2) {
return true;
}
const obj1Keys = Object.keys(obj1);
const obj2Keys = Object.keys(obj2);
if (obj1Keys.length !== obj2Keys.length) {
return false;
}
for(let key in obj1) {
const res = isEqual(obj1[key], obj2[key]);
if (!res) {
return false;
}
}
return true;
}