手写 bind 函数
知识点:
this 指向(作用域), this的指向是在调用时确定的, 由调用当前方法的对象决定;
不同于自由变量,自由变量的作用域决定于 其本身定义的位置。
分析:bind 函数的特点
1. 改变 this 指向
2. 参数以数组形式表示
3. 返回一个函数
Function.prototype.bind1 = function (self, ...rest) {
// 函数的this指向是在调用时确定的,因此,此处的this实际就是调用bind1函数的函数对应的this
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();
// 以下供post 请求使用
// const postData = {
// a: 1,
// b: 2
// }
// xhr.send(JSON.stringify(postData));
手写 简易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) // false
// 如果 对象或数组 的内容完全相同,如何判断他们相等
// 1. 是否是对象(不包括函数)
function isObject() {
return typeof obj === 'object' && obj !== null
}
// 2. 比较
function isEqual(obj1, obj2) {
// 考虑值类型
if (!isObject(obj1) || !isObject(obj2)) {
return obj1 === obj2;
}
// 如果 obj1 和 obj2 本身就是同一个对象
if (obj1 === obj2) {
return true;
}
// 两个都是对象,且不相等
// 1. 先取出两个对象的 keys, 比较keys的个数
const obj1Keys = Object.keys(obj1);
const obj2Keys = Object.keys(obj2);
if (obj1Keys.length !== obj2Keys.length) {
return false;
}
// 2. 以obj1为基准,和 obj2 依次 递归比较
for(let key in obj1) {
// 比较当前 key 的 val --递归
const res = isEqual(obj1[key], obj2[key]);
if (!res) {
return false;
}
}
// 3. 全相等
return true;
}