死磕 36 个 JS 手写题(搞懂后,提升真的大)

防抖的原理就是:就是要等你触发完事件n秒内不再触发事件,才执行。

  1. // 第一版
  2. function debounce(func, wait) {
  3. var timeout;
  4. return function () {
  5. clearTimeout(timeout)
  6. timeout = setTimeout(func, wait);
  7. }
  8. }

节流的原理:如果你持续触发事件,每隔一段时间,只执行一次事件。

  1. // 第二版
  2. function throttle(func, wait) {
  3. var timeout;
  4. var previous = 0;
  5. return function() {
  6. context = this;
  7. args = arguments;
  8. if (!timeout) {
  9. timeout = setTimeout(function(){
  10. timeout = null;
  11. func.apply(context, args)
  12. }, wait)
  13. }
  14. }
  15. }

深浅拷贝

image.png

数据类型存储

js中存在两大数据类型

  • 基本类型
  • 引用类型

基本类型数据保存在在栈内存中
引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中

浅拷贝

  1. function shallowClone(obj) {
  2. const newObj = {};
  3. for(let prop in obj) {
  4. if(obj.hasOwnProperty(prop)){
  5. newObj[prop] = obj[prop];
  6. }
  7. }
  8. return newObj;
  9. }
  • Object.assign
  • slice()
  • concat()
  • 拓展运算符

    深拷贝

  • JSON.stringify()

    1. const obj2=JSON.parse(JSON.stringify(obj1));

    会忽略 undefined symbol 函数

  • 递归

    1. var deepCopy = function(obj) {
    2. if (typeof obj !== 'object') return;
    3. var newObj = obj instanceof Array ? [] : {};
    4. for (var key in obj) {
    5. if (obj.hasOwnProperty(key)) {
    6. newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
    7. }
    8. }
    9. return newObj;
    10. }

    ``` const isComplexDataType = obj => (typeof obj === ‘object’ || typeof obj === ‘function’) && (obj !== null)

const deepClone = function (obj, hash = new WeakMap()) {

if (obj.constructor === Date)

return new Date(obj) // 日期对象直接返回一个新的日期对象

if (obj.constructor === RegExp)

return new RegExp(obj) //正则对象直接返回一个新的正则对象

//如果循环引用了就用 weakMap 来解决

if (hash.has(obj)) return hash.get(obj)

// 可以获得对象的所有属性 let allDesc = Object.getOwnPropertyDescriptors(obj)

//遍历传入参数所有键的特性

let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)

//继承原型链

hash.set(obj, cloneObj) // 获取 不可枚举属性以及 Symbol 类型 for (let key of Reflect.ownKeys(obj)) {

  1. cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]

}

return cloneObj

}

// 下面是验证代码

let obj = {

num: 0,

str: ‘’,

boolean: true,

unf: undefined,

nul: null,

obj: { name: ‘我是一个对象’, id: 1 },

arr: [0, 1, 2],

func: function () { console.log(‘我是一个函数’) },

date: new Date(0),

reg: new RegExp(‘/我是一个正则/ig’),

};

Object.defineProperty(obj, ‘innumerable’, {

enumerable: false, value: ‘不可枚举属性’ }

);

obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))

obj.loop = obj // 设置loop成循环引用的属性

let cloneObj = deepClone(obj)

cloneObj.arr.push(4)

console.log(‘obj’, obj)

console.log(‘cloneObj’, cloneObj)

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/756869/1653725163301-7123bbe6-d46f-4860-aac4-713cb2c40ec9.png#clientId=ub3dd323a-afc2-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=uc9672a53&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1110&originWidth=1080&originalType=binary&ratio=1&rotation=0&showTitle=false&size=189322&status=done&style=none&taskId=u6cec3121-e96a-4193-bafd-bb2979105f9&title=)<br />改进后的优点
  2. 1. 针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys 方法;
  3. 2. 当参数为 DateRegExp 类型,则直接生成一个新的实例返回;
  4. 3. 利用 Object getOwnPropertyDescriptors 方法可以获得对象的所有属性,以及对应的特性,顺便结合 Object create 方法创建一个新对象,并继承传入原对象的原型链;
  5. 4. 利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏(你可以关注一下 Map weakMap 的关键区别,这里要用 weakMap),作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值。
  6. <a name="paFbz"></a>
  7. ### 手写new
  8. new 关键词的主要作用就是执行一个构造函数,返回一个实例对象,在new 的过程中,根据构造函数的情况,来确定是否可以接受参数的传递。
  9. 1. 创建一个新对象;
  10. 2. 将构造函数的作用域赋给新对象(this 指向新对象);
  11. 3. 执行构造函数中的代码(为这个新对象添加属性);
  12. 4. 返回新对象。

// 第二版的代码 function objectFactory() {

  1. var obj = new Object(),
  2. Constructor = [].shift.call(arguments);
  3. obj.__proto__ = Constructor.prototype;
  4. var ret = Constructor.apply(obj, arguments);
  5. return typeof ret === 'object' ? ret : obj;

};

  1. <a name="ZyEh3"></a>
  2. ### curry化分析
  3. 简单来说,就是在一个函数中预先填充几个参数,这个函数返回另一个函数,这个返回的新函数将其参数和预先填充的参数进行合并,再执行函数逻辑

const curry = (fn, length) => { length = length || fn.length return function (…args) { if (args.length < length) { return curry(fn.bind(this, …args), length - args.length) } else { return fn.call(this, …args) } } }

const curry = fn => { return tempFn = (…arg1) => { if (arg1.length >= fn.length) { return fn(…arg1) } else { return (…arg2) => tempFn(…arg1, …arg2) } } }

  1. <a name="ZLflV"></a>
  2. ### 手写bind

Function.prototype.bind2 = function (context) {

  1. if (typeof this !== "function") {
  2. throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
  3. }
  4. var self = this;
  5. var args = Array.prototype.slice.call(arguments, 1);
  6. var fNOP = function () {};
  7. var fBound = function () {
  8. // bind后的函数,可能会作为 构造函数使用
  9. var bindArgs = Array.prototype.slice.call(arguments);
  10. return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
  11. }
  12. fNOP.prototype = this.prototype;
  13. fBound.prototype = new fNOP();
  14. return fBound;

}

  1. <a name="yhMjj"></a>
  2. ### js 的大数相加结果错误问题
  3. 因为JavaScript的Number类型是遵循IEEE 754规范表示的<br />JavaScript能精确表示的数字是有限的,JavaScript可以精确到个位的最大整数为2的53次方,超过这个范围就会精度丢失,造成JavaScript无法判断大小,从而会出现下面的<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/756869/1657524265063-826f796d-f94a-449b-961d-5c83a94ebe4f.png#clientId=u1702b8b1-6ede-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=uafb6c89f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=52&originWidth=302&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5465&status=done&style=none&taskId=u88df4bcd-063c-4ec2-b26b-c736803c154&title=)<br />如何解决呢,这里我的解决思路是采用BigInt(a)来将a转化为大整数,然后在计算过程中都要用大数的计算规则,数字后+n,这是因为大数不能和普通的number类型进行运算否则会报错,然后将最后的计算结果toString转化为字符串形式,注意不能使用parseInt和Number转化为数值类型,这样的话因为最后返回的依然是number类型依旧会失去精度

// 对于大数问题,可以用BigInt()和toString来解决 let b = BigInt(“9007199254740992”) //这里传入的是字符串是因为Oj在读取每行内容的时候是以字符串形式读取的,当然参数也可以是数值型 b = b + 1n console.log(Number(b));//9007199254740992 console.log(parseInt(b)); //9007199254740992 // toString方法将大数变成字符串,且大数的最后一位没有n console.log(b.toString()); //9007199254740993

```