发布于 2024 年 2 月 26 日,星期一
"手撕代码系列(一)"这个标题暗示了博客内容将深入探讨前端代码的实现细节。它强调通过手动编写代码来理解和掌握前端技术,而非仅仅依赖框架或库。这种做法有助于开发者深入理解代码背后的逻辑和原理,提升编程技能。通过"手撕"这一动作,作者可能希望传达一种亲自动手、深入实践的学习态度,鼓励读者在实践中发现问题、解决问题,从而达到更高的技术水平。这种学习方式不仅有助于记忆和理解,还能培养独立思考和解决问题的能力。
系列首发于公众号『非同质前端札记』https://mp.weixin.qq.com/s?__biz=MzkyOTI2MzE0MQ==&mid=2247485576&idx=1&sn=5ddfe93f427f05f5d126dead859d0dc8&chksm=c20d73c2f57afad4bbea380dfa1bcc15367a4cc06bf5dd0603100e8bd7bb317009fa65442cdb&token=1071012447&lang=zh_CN#rd ,若不想错过更多精彩内容,请“星标”一下,敬请关注公众号最新消息。
/**
* deepClone 深拷贝
* @param {*} source 源对象(需要拷贝的对象)
* @returns 新对象
*/const deepClone = source => { // 存储结果 const obj = {}; // for...in 方法:遍历对象时 key 为对象的键;遍历数组时 key 为数组下标 index for (const key in source) { // Object.prototype.hasOwnProperty.call(); 方法是一个常用的,安全监测对象是否含有某个属性的方法,使用此方法可避免 hasOwnProperty 属性被污染或被重写的风险。 if (Object.prototype.hasOwnProperty.call(source, key)) { // Object.prototype.toString.call(); 通过原型的方式判断一个值的类型 // 为什么使用 xxx.slice(8, -1)? // 因为 Object.prototype.toString.call() 返回的值是 [object Object], 使用 slice 方法截取 if (Object.prototype.toString.call(source[key]).slice(8, -1) === 'Object') { // 递归 obj[key] = deepClone(source[key]); } else { // 赋值 obj[key] = source[key]; } } } return obj;};// test:const a = { age: 12, otherInfo: { sex: 0, },};const b = deepClone(a);b.age = 22;b.otherInfo.sex = 1;console.log('a------>', a); // a------> { age: 12, otherInfo: { sex: 0 } }console.log('b------>', b); // b------> { age: 22, otherInfo: { sex: 1 } }
/**
* shallowClone 浅拷贝
* @param {*} source 源对象(需要拷贝的对象)
* @returns 新对象
*/const shallowClone = source => { const obj = {}; for (const key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { obj[key] = source[key]; } } return obj;};// test:const a = { age: 12, otherInfo: { sex: 0, },};const b = shallowClone(a);b.age = 22;b.otherInfo.sex = 1;console.log('a------>', a); // a------> { age: 12, otherInfo: { sex: 1 } }console.log('b------>', b); // b------> { age: 22, otherInfo: { sex: 1 } }
/**
* customNew new操作符
* @param {*} fn 普通函数(!不能是箭头函数)
* @param {*} rest 参数
* @returns 新对象(类似于实例)
*/const customNew = (fn, ...rest) => { // 1.创建一个新对象 const obj = {}; // 2.让新对象的隐士原型等于函数的显示原型 obj.__proto__ = fn.prototype; // 3.绑定 this, 让函数的 this 指向这个新对象(也就是绑定一些属性) const res = fn.apply(obj, rest); // 4.判断返回 return Object.prototype.toString.call(res).slice(8, -1) === 'Object' ? res : obj;};// test:function Person(name, age) { this.name = name; this.age = age;}Person.prototype.getName = function () { return this.name;};const person = customNew(Person, 'John', 11);console.log('person ------->', person); // person -------> Person { name: 'John', age: 11 }console.log('person.getName() ------->', person.getName()); // person.getName() -------> John
/**
* throttle 节流
* 某一段时间内只触发一次,按第一次来算
* @param {*} fn 方法
* @param {*} time 时间(默认 1500ms)
* @returns
*/const throttle = (fn, time = 1500) => { // 标记状态 let flag = null; return function () { // 若 flag 为 true, 则继续执行,不做任何操作 if (flag) return; // 更改标记状态 flag = true; // 执行方法 fn(); // 定时器 let timer = setTimeout(() => { // time 结束,修改 flag = false flag = false; // 清除定时器标记 clearTimeout(timer); }, time); };};
/**
* debounce 防抖
* 某一段时间内只触发一次,按最后一次来算
* @param {*} fn 方法
* @param {*} time 时间(默认 1500ms)
* @returns
*/const debounce = (fn, time = 1500) => { // 定期器标记 let timer = null; return function () { // 若 timer 为 true, 则停止之前方法,重新开始 timer && clearTimeout(timer); timer = setTimeout(() => { // 执行方法 fn(); }, time); }};
Q:(question)
R:(result)
A:(attention matters)
D:(detail info)
S:(summary)
Ana:(analysis)
T:(tips)