关于前端:前端面试遇到了这些手写题

34次阅读

共计 18684 个字符,预计需要花费 47 分钟才能阅读完成。

用正则写一个依据 name 获取 cookie 中的值的办法

function getCookie(name) {var match = document.cookie.match(new RegExp('(^|)' + name + '=([^;]*)'));
  if (match) return unescape(match[2]);
}
  1. 获取页面上的 cookie 能够应用 document.cookie

这里获取到的是相似于这样的字符串:

'username=poetry; user-id=12345; user-roles=home, me, setting'

能够看到这么几个信息:

  • 每一个 cookie 都是由 name=value 这样的模式存储的
  • 每一项的结尾可能是一个空串 ''(比方username 的结尾其实就是), 也可能是一个空字符串 ' '(比方user-id 的结尾就是)
  • 每一项用 ";" 来辨别
  • 如果某项中有多个值的时候,是用 "," 来连贯的 (比方user-roles 的值)
  • 每一项的结尾可能是有 ";" 的(比方 username 的结尾),也可能是没有的 (比方user-roles 的结尾)
  • 所以咱们将这里的正则拆分一下:
  • '(^|)'示意的就是获取每一项的结尾,因为咱们晓得如果 ^ 不是放在 [] 里的话就是示意结尾匹配。所以这里 (^|) 的意思其实就被拆分为 (^) 示意的匹配 username 这种状况,它后面什么都没有是一个空串 (你能够把(^) 了解为 ^ 它前面还有一个暗藏的 '');而| 示意的就是或者是一个 " "(为了匹配user-id 结尾的这种状况)
  • +name+这没什么好说的
  • =([^;]*)这里匹配的就是 = 前面的值了,比方 poetry;刚刚说了^ 要是放在 [] 里的话就示意 "除了 ^ 前面的内容都能匹配",也就是非的意思。所以这里([^;]*) 示意的是除了 ";" 这个字符串别的都匹配 (* 应该都晓得什么意思吧,匹配 0 次或屡次)
  • 有的大佬等号前面是这样写的 '=([^;]*)(;|$)',而最初为什么能够把'(;|$)' 给省略呢?因为其实最初一个 cookie 项是没有 ';' 的,所以它能够合并到 =([^;]*) 这一步。
  • 最初获取到的 match 其实是一个长度为 4 的数组。比方:
[
  "username=poetry;",
  "","poetry",";"
]
  • 第 0 项:全量
  • 第 1 项:结尾
  • 第 2 项:两头的值
  • 第 3 项:结尾

所以咱们是要拿第 2 项 match[2] 的值。

  1. 为了避免获取到的值是 %xxx 这样的字符序列,须要用 unescape() 办法解码。

前端手写面试题具体解答

滚动加载

原理就是监听页面滚动事件,剖析 clientHeightscrollTopscrollHeight三者的属性关系。

window.addEventListener('scroll', function() {
  const clientHeight = document.documentElement.clientHeight;
  const scrollTop = document.documentElement.scrollTop;
  const scrollHeight = document.documentElement.scrollHeight;
  if (clientHeight + scrollTop >= scrollHeight) {
    // 检测到滚动至页面底部,进行后续操作
    // ...
  }
}, false);

实现迭代器生成函数

咱们说 迭代器对象 全凭 迭代器生成函数 帮咱们生成。在 ES6 中,实现一个迭代器生成函数并不是什么难事儿,因为 ES6 早帮咱们思考好了全套的解决方案,内置了贴心的 生成器Generator)供咱们应用:

// 编写一个迭代器生成函数
function *iteratorGenerator() {
    yield '1 号选手'
    yield '2 号选手'
    yield '3 号选手'
}

const iterator = iteratorGenerator()

iterator.next()
iterator.next()
iterator.next()

丢进控制台,不负众望:

写一个生成器函数并没有什么难度,但在面试的过程中,面试官往往对生成器这种语法糖背地的实现逻辑更感兴趣。上面咱们要做的,不仅仅是写一个迭代器对象,而是用 ES5 去写一个可能生成迭代器对象的迭代器生成函数(解析在正文里):

// 定义生成器函数,入参是任意汇合
function iteratorGenerator(list) {
    // idx 记录以后拜访的索引
    var idx = 0
    // len 记录传入汇合的长度
    var len = list.length
    return {
        // 自定义 next 办法
        next: function() {
            // 如果索引还没有超出汇合长度,done 为 false
            var done = idx >= len
            // 如果 done 为 false,则能够持续取值
            var value = !done ? list[idx++] : undefined

            // 将以后值与遍历是否结束(done)返回
            return {
                done: done,
                value: value
            }
        }
    }
}

var iterator = iteratorGenerator(['1 号选手', '2 号选手', '3 号选手'])
iterator.next()
iterator.next()
iterator.next()

此处为了记录每次遍历的地位,咱们实现了一个闭包,借助自在变量来做咱们的迭代过程中的“游标”。

运行一下咱们自定义的迭代器,后果合乎预期:

实现 Ajax

步骤

  • 创立 XMLHttpRequest 实例
  • 收回 HTTP 申请
  • 服务器返回 XML 格局的字符串
  • JS 解析 XML,并更新部分页面
  • 不过随着历史进程的推动,XML 曾经被淘汰,取而代之的是 JSON。

理解了属性和办法之后,依据 AJAX 的步骤,手写最简略的 GET 申请。

实现每隔一秒打印 1,2,3,4

// 应用闭包实现
for (var i = 0; i < 5; i++) {(function(i) {setTimeout(function() {console.log(i);
    }, i * 1000);
  })(i);
}
// 应用 let 块级作用域
for (let i = 0; i < 5; i++) {setTimeout(function() {console.log(i);
  }, i * 1000);
}

数组去重

const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];
// => [1, '1', 17, true, false, 'true', 'a', {}, {}]
办法一:利用 Set
const res1 = Array.from(new Set(arr));
办法二:两层 for 循环 +splice
const unique1 = arr => {
  let len = arr.length;
  for (let i = 0; i < len; i++) {for (let j = i + 1; j < len; j++) {if (arr[i] === arr[j]) {arr.splice(j, 1);
        // 每删除一个树,j-- 保障 j 的值通过自加后不变。同时,len--,缩小循环次数晋升性能
        len--;
        j--;
      }
    }
  }
  return arr;
}
办法三:利用 indexOf
const unique2 = arr => {const res = [];
  for (let i = 0; i < arr.length; i++) {if (res.indexOf(arr[i]) === -1) res.push(arr[i]);
  }
  return res;
}

当然也能够用 include、filter,思路大同小异。

办法四:利用 include
const unique3 = arr => {const res = [];
  for (let i = 0; i < arr.length; i++) {if (!res.includes(arr[i])) res.push(arr[i]);
  }
  return res;
}
办法五:利用 filter
const unique4 = arr => {return arr.filter((item, index) => {return arr.indexOf(item) === index;
  });
}
办法六:利用 Map
const unique5 = arr => {const map = new Map();
  const res = [];
  for (let i = 0; i < arr.length; i++) {if (!map.has(arr[i])) {map.set(arr[i], true)
      res.push(arr[i]);
    }
  }
  return res;
}

深拷贝

递归的残缺版本(思考到了 Symbol 属性):

const cloneDeep1 = (target, hash = new WeakMap()) => {
  // 对于传入参数解决
  if (typeof target !== 'object' || target === null) {return target;}
  // 哈希表中存在间接返回
  if (hash.has(target)) return hash.get(target);

  const cloneTarget = Array.isArray(target) ? [] : {};
  hash.set(target, cloneTarget);

  // 针对 Symbol 属性
  const symKeys = Object.getOwnPropertySymbols(target);
  if (symKeys.length) {
    symKeys.forEach(symKey => {if (typeof target[symKey] === 'object' && target[symKey] !== null) {cloneTarget[symKey] = cloneDeep1(target[symKey]);
      } else {cloneTarget[symKey] = target[symKey];
      }
    })
  }

  for (const i in target) {if (Object.prototype.hasOwnProperty.call(target, i)) {cloneTarget[i] =
        typeof target[i] === 'object' && target[i] !== null
        ? cloneDeep1(target[i], hash)
        : target[i];
    }
  }
  return cloneTarget;
}

原型继承

这里只写寄生组合继承了,两头还有几个演变过去的继承但都有一些缺点

function Parent() {this.name = 'parent';}
function Child() {Parent.call(this);
  this.type = 'children';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

实现类数组转化为数组

类数组转换为数组的办法有这样几种:

  • 通过 call 调用数组的 slice 办法来实现转换
Array.prototype.slice.call(arrayLike);
  • 通过 call 调用数组的 splice 办法来实现转换
Array.prototype.splice.call(arrayLike, 0);
  • 通过 apply 调用数组的 concat 办法来实现转换
Array.prototype.concat.apply([], arrayLike);
  • 通过 Array.from 办法来实现转换
Array.from(arrayLike);

打印出以后网页应用了多少种 HTML 元素

一行代码能够解决:

const fn = () => {return [...new Set([...document.querySelectorAll('*')].map(el => el.tagName))].length;
}

值得注意的是:DOM 操作返回的是 类数组,须要转换为数组之后才能够调用数组的办法。

实现节流函数(throttle)

节流函数原理: 指频繁触发事件时,只会在指定的时间段内执行事件回调,即触发事件间隔大于等于指定的工夫才会执行回调函数。总结起来就是:事件,依照一段时间的距离来进行触发

像 dom 的拖拽,如果用消抖的话,就会呈现卡顿的感觉,因为只在进行的时候执行了一次,这个时候就应该用节流,在肯定工夫内屡次执行,会晦涩很多

手写简版

应用工夫戳的节流函数会在第一次触发事件时立刻执行,当前每过 wait 秒之后才执行一次,并且最初一次触发事件不会被执行

工夫戳形式:

// func 是用户传入须要防抖的函数
// wait 是等待时间
const throttle = (func, wait = 50) => {
  // 上一次执行该函数的工夫
  let lastTime = 0
  return function(...args) {
    // 以后工夫
    let now = +new Date()
    // 将以后工夫和上一次执行函数工夫比照
    // 如果差值大于设置的等待时间就执行函数
    if (now - lastTime > wait) {
      lastTime = now
      func.apply(this, args)
    }
  }
}

setInterval(throttle(() => {console.log(1)
  }, 500),
  1
)

定时器形式:

应用定时器的节流函数在第一次触发时不会执行,而是在 delay 秒之后才执行,当最初一次进行触发后,还会再执行一次函数

function throttle(func, delay){
  var timer = null;
  returnfunction(){
    var context = this;
    var args = arguments;
    if(!timer){timer = setTimeout(function(){func.apply(context, args);
        timer = null;
      },delay);
    }
  }
}

实用场景:

  • DOM 元素的拖拽性能实现(mousemove
  • 搜寻联想(keyup
  • 计算鼠标挪动的间隔(mousemove
  • Canvas 模仿画板性能(mousemove
  • 监听滚动事件判断是否到页面底部主动加载更多
  • 拖拽场景:固定工夫内只执行一次,避免超高频次触发地位变动
  • 缩放场景:监控浏览器resize
  • 动画场景:防止短时间内屡次触发动画引起性能问题

总结

  • 函数防抖:将几次操作合并为一次操作进行。原理是保护一个计时器,规定在 delay 工夫后触发函数,然而在 delay 工夫内再次触发的话,就会勾销之前的计时器而从新设置。这样一来,只有最初一次操作能被触发。
  • 函数节流:使得肯定工夫内只触发一次函数。原理是通过判断是否达到肯定工夫来触发函数。

Array.prototype.map()

Array.prototype.map = function(callback, thisArg) {if (this == undefined) {throw new TypeError('this is null or not defined');
  }
  if (typeof callback !== 'function') {throw new TypeError(callback + 'is not a function');
  }
  const res = [];
  // 同理
  const O = Object(this);
  const len = O.length >>> 0;
  for (let i = 0; i < len; i++) {if (i in O) {
      // 调用回调函数并传入新数组
      res[i] = callback.call(thisArg, O[i], i, this);
    }
  }
  return res;
}

Object.is

Object.is解决的次要是这两个问题:

+0 === -0  // true
NaN === NaN // false
const is= (x, y) => {if (x === y) {
    // + 0 和 - 0 应该不相等
    return x !== 0 || y !== 0 || 1/x === 1/y;
  } else {return x !== x && y !== y;}
}

debounce(防抖)

触发高频工夫后 n 秒内函数只会执行一次, 如果 n 秒内高频工夫再次触发, 则从新计算工夫。

const debounce = (fn, time) => {
  let timeout = null;
  return function() {clearTimeout(timeout)
    timeout = setTimeout(() => {fn.apply(this, arguments);
    }, time);
  }
};

防抖常利用于用户进行搜寻输出节约申请资源,window触发 resize 事件时进行防抖只触发一次。

实现 ES6 的 extends

function B(name){this.name = name;};
function A(name,age){
  //1. 将 A 的原型指向 B
  Object.setPrototypeOf(A,B);
  //2. 用 A 的实例作为 this 调用 B, 失去继承 B 之后的实例,这一步相当于调用 super
  Object.getPrototypeOf(A).call(this, name)
  //3. 将 A 原有的属性增加到新实例上
  this.age = age; 
  //4. 返回新实例对象
  return this;
};
var a = new A('poetry',22);
console.log(a);

查找文章中呈现频率最高的单词

function findMostWord(article) {
  // 合法性判断
  if (!article) return;
  // 参数解决
  article = article.trim().toLowerCase();
  let wordList = article.match(/[a-z]+/g),
    visited = [],
    maxNum = 0,
    maxWord = "";
  article = "" + wordList.join("  ") +" ";
  // 遍历判断单词呈现次数
  wordList.forEach(function(item) {if (visited.indexOf(item) < 0) {
      // 退出 visited 
      visited.push(item);
      let word = new RegExp("" + item +" ","g"),
        num = article.match(word).length;
      if (num > maxNum) {
        maxNum = num;
        maxWord = item;
      }
    }
  });
  return maxWord + " " + maxNum;
}

实现深拷贝

简洁版本

简略版:

const newObj = JSON.parse(JSON.stringify(oldObj));

局限性:

  • 他无奈实现对函数、RegExp 等非凡对象的克隆
  • 会摈弃对象的constructor, 所有的构造函数会指向Object
  • 对象有循环援用, 会报错

面试简版

function deepClone(obj) {
    // 如果是 值类型 或 null,则间接 return
    if(typeof obj !== 'object' || obj === null) {return obj}

    // 定义后果对象
    let copy = {}

    // 如果对象是数组,则定义后果数组
    if(obj.constructor === Array) {copy = []
    }

    // 遍历对象的 key
    for(let key in obj) {
        // 如果 key 是对象的自有属性
        if(obj.hasOwnProperty(key)) {
          // 递归调用深拷贝办法
          copy[key] = deepClone(obj[key])
        }
    }

    return copy
} 

调用深拷贝办法,若属性为值类型,则间接返回;若属性为援用类型,则递归遍历。这就是咱们在解这一类题时的外围的办法。

进阶版

  • 解决拷贝循环援用问题
  • 解决拷贝对应原型问题
// 递归拷贝 (类型判断)
function deepClone(value,hash = new WeakMap){ // 弱援用,不必 map,weakMap 更适合一点
  // null 和 undefiend 是不须要拷贝的
  if(value == null){return value;}
  if(value instanceof RegExp) {return new RegExp(value) }
  if(value instanceof Date) {return new Date(value) }
  // 函数是不须要拷贝
  if(typeof value != 'object') return value;
  let obj = new value.constructor(); // [] {}
  // 阐明是一个对象类型
  if(hash.get(value)){return hash.get(value)
  }
  hash.set(value,obj);
  for(let key in value){ // in 会遍历以后对象上的属性 和 __proto__指代的属性
    // 补拷贝 对象的__proto__上的属性
    if(value.hasOwnProperty(key)){
      // 如果值还有可能是对象 就持续拷贝
      obj[key] = deepClone(value[key],hash);
    }
  }
  return obj
  // 辨别对象和数组 Object.prototype.toString.call
}
// test

var o = {};
o.x = o;
var o1 = deepClone(o); // 如果这个对象拷贝过了 就返回那个拷贝的后果就能够了
console.log(o1);

实现残缺的深拷贝

1. 简易版及问题

JSON.parse(JSON.stringify());

预计这个 api 能笼罩大多数的利用场景,没错,谈到深拷贝,我第一个想到的也是它。然而实际上,对于某些严格的场景来说,这个办法是有微小的坑的。问题如下:

  1. 无奈解决 循环援用 的问题。举个例子:
const a = {val:2};
a.target = a;

拷贝 a 会呈现零碎栈溢出,因为呈现了有限递归的状况。

  1. 无奈拷贝一些非凡的对象,诸如 RegExp, Date, Set, Map
  2. 无奈拷贝 函数(划重点)。

因而这个 api 先 pass 掉,咱们从新写一个深拷贝,简易版如下:

const deepClone = (target) => {if (typeof target === 'object' && target !== null) {const cloneTarget = Array.isArray(target) ? []: {};
    for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = deepClone(target[prop]);
      }
    }
    return cloneTarget;
  } else {return target;}
}

当初,咱们以刚刚发现的三个问题为导向,一步步来欠缺、优化咱们的深拷贝代码。

2. 解决循环援用

当初问题如下:

let obj = {val : 100};
obj.target = obj;

deepClone(obj);// 报错: RangeError: Maximum call stack size exceeded

这就是循环援用。咱们怎么来解决这个问题呢?

创立一个 Map。记录下曾经拷贝过的对象,如果说曾经拷贝过,那间接返回它行了。

const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;

const deepClone = (target, map = new Map()) => {if(map.get(target))  
    return target; 


  if (isObject(target)) {map.set(target, true); 
    const cloneTarget = Array.isArray(target) ? []: {}; 
    for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = deepClone(target[prop],map); 
      } 
    } 
    return cloneTarget; 
  } else {return target;} 
}

当初来试一试:

const a = {val:2};
a.target = a;
let newA = deepClone(a);
console.log(newA)//{val: 2, target: { val: 2, target: [Circular] } }

如同是没有问题了, 拷贝也实现了。但还是有一个潜在的坑, 就是 map 上的 key 和 map 形成了强援用关系,这是相当危险的。我给你解释一下与之绝对的弱援用的概念你就明确了

在计算机程序设计中,弱援用与强援用绝对,

被弱援用的对象能够在任何时候被回收,而对于强援用来说,只有这个强援用还在,那么对象无奈被回收。拿下面的例子说,map 和 a 始终是强援用的关系,在程序完结之前,a 所占的内存空间始终不会被开释。

怎么解决这个问题?

很简略,让 map 的 key 和 map 形成弱援用即可。ES6 给咱们提供了这样的数据结构,它的名字叫 WeakMap,它是一种非凡的 Map, 其中的键是弱援用的。其键必须是对象,而值能够是任意的

略微革新一下即可:

const deepClone = (target, map = new WeakMap()) => {//...}

3. 拷贝非凡对象

可持续遍历

对于非凡的对象,咱们应用以下形式来甄别:

Object.prototype.toString.call(obj);

梳理一下对于可遍历对象会有什么后果:

["object Map"]
["object Set"]
["object Array"]
["object Object"]
["object Arguments"]

以这些不同的字符串为根据,咱们就能够胜利地甄别这些对象。

const getType = Object.prototype.toString.call(obj);

const canTraverse = {'[object Map]': true,
  '[object Set]': true,
  '[object Array]': true,
  '[object Object]': true,
  '[object Arguments]': true,
};

const deepClone = (target, map = new Map()) => {if(!isObject(target)) 
    return target;
  let type = getType(target);
  let cloneTarget;
  if(!canTraverse[type]) {
    // 解决不能遍历的对象
    return;
  }else {
    // 这波操作相当要害,能够保障对象的原型不失落!let ctor = target.prototype;
    cloneTarget = new ctor();}

  if(map.get(target)) 
    return target;
  map.put(target, true);

  if(type === mapTag) {
    // 解决 Map
    target.forEach((item, key) => {cloneTarget.set(deepClone(key), deepClone(item));
    })
  }

  if(type === setTag) {
    // 解决 Set
    target.forEach(item => {target.add(deepClone(item));
    })
  }

  // 解决数组和对象
  for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = deepClone(target[prop]);
    }
  }
  return cloneTarget;
}

不可遍历的对象

const boolTag = '[object Boolean]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';

对于不可遍历的对象,不同的对象有不同的解决。

const handleRegExp = (target) => {const { source, flags} = target;
  return new target.constructor(source, flags);
}

const handleFunc = (target) => {// 待会的重点局部}

const handleNotTraverse = (target, tag) => {
  const Ctor = targe.constructor;
  switch(tag) {
    case boolTag:
    case numberTag:
    case stringTag:
    case errorTag: 
    case dateTag:
      return new Ctor(target);
    case regexpTag:
      return handleRegExp(target);
    case funcTag:
      return handleFunc(target);
    default:
      return new Ctor(target);
  }
}

4. 拷贝函数

  • 尽管函数也是对象,然而它过于非凡,咱们独自把它拿进去拆解。
  • 提到函数,在 JS 种有两种函数,一种是一般函数,另一种是箭头函数。每个一般函数都是
  • Function 的实例,而箭头函数不是任何类的实例,每次调用都是不一样的援用。那咱们只须要
  • 解决一般函数的状况,箭头函数间接返回它自身就好了。

那么如何来辨别两者呢?

答案是: 利用原型。箭头函数是不存在原型的。

const handleFunc = (func) => {
  // 箭头函数间接返回本身
  if(!func.prototype) return func;
  const bodyReg = /(?<={)(.|\n)+(?=})/m;
  const paramReg = /(?<=\().+(?=\)\s+{)/;
  const funcString = func.toString();
  // 别离匹配 函数参数 和 函数体
  const param = paramReg.exec(funcString);
  const body = bodyReg.exec(funcString);
  if(!body) return null;
  if (param) {const paramArr = param[0].split(',');
    return new Function(...paramArr, body[0]);
  } else {return new Function(body[0]);
  }
}

5. 残缺代码展现

const getType = obj => Object.prototype.toString.call(obj);

const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;

const canTraverse = {'[object Map]': true,
  '[object Set]': true,
  '[object Array]': true,
  '[object Object]': true,
  '[object Arguments]': true,
};
const mapTag = '[object Map]';
const setTag = '[object Set]';
const boolTag = '[object Boolean]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';

const handleRegExp = (target) => {const { source, flags} = target;
  return new target.constructor(source, flags);
}

const handleFunc = (func) => {
  // 箭头函数间接返回本身
  if(!func.prototype) return func;
  const bodyReg = /(?<={)(.|\n)+(?=})/m;
  const paramReg = /(?<=\().+(?=\)\s+{)/;
  const funcString = func.toString();
  // 别离匹配 函数参数 和 函数体
  const param = paramReg.exec(funcString);
  const body = bodyReg.exec(funcString);
  if(!body) return null;
  if (param) {const paramArr = param[0].split(',');
    return new Function(...paramArr, body[0]);
  } else {return new Function(body[0]);
  }
}

const handleNotTraverse = (target, tag) => {
  const Ctor = target.constructor;
  switch(tag) {
    case boolTag:
      return new Object(Boolean.prototype.valueOf.call(target));
    case numberTag:
      return new Object(Number.prototype.valueOf.call(target));
    case stringTag:
      return new Object(String.prototype.valueOf.call(target));
    case symbolTag:
      return new Object(Symbol.prototype.valueOf.call(target));
    case errorTag: 
    case dateTag:
      return new Ctor(target);
    case regexpTag:
      return handleRegExp(target);
    case funcTag:
      return handleFunc(target);
    default:
      return new Ctor(target);
  }
}

const deepClone = (target, map = new WeakMap()) => {if(!isObject(target)) 
    return target;
  let type = getType(target);
  let cloneTarget;
  if(!canTraverse[type]) {
    // 解决不能遍历的对象
    return handleNotTraverse(target, type);
  }else {
    // 这波操作相当要害,能够保障对象的原型不失落!let ctor = target.constructor;
    cloneTarget = new ctor();}

  if(map.get(target)) 
    return target;
  map.set(target, true);

  if(type === mapTag) {
    // 解决 Map
    target.forEach((item, key) => {cloneTarget.set(deepClone(key, map), deepClone(item, map));
    })
  }

  if(type === setTag) {
    // 解决 Set
    target.forEach(item => {cloneTarget.add(deepClone(item, map));
    })
  }

  // 解决数组和对象
  for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = deepClone(target[prop], map);
    }
  }
  return cloneTarget;
}

应用 reduce 求和

arr = [1,2,3,4,5,6,7,8,9,10],求和

let arr = [1,2,3,4,5,6,7,8,9,10]
arr.reduce((prev, cur) => {return prev + cur}, 0)

arr = [1,2,3,[[4,5],6],7,8,9],求和

let arr = [1,2,3,4,5,6,7,8,9,10]
arr.flat(Infinity).reduce((prev, cur) => {return prev + cur}, 0)

arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和

let arr = [{a:9, b:3, c:4}, {a:1, b:3}, {a:3}] 

arr.reduce((prev, cur) => {return prev + cur["a"];
}, 0)

实现一个繁难的 MVVM

实现一个繁难的 MVVM 我会分为这么几步来:

  1. 首先我会定义一个类 Vue,这个类接管的是一个options,那么其中可能有须要挂载的根元素的id,也就是el 属性;而后应该还有一个 data 属性,示意须要双向绑定的数据
  2. 其次我会定义一个 Dep 类,这个类产生的实例对象中会定义一个 subs 数组用来寄存所依赖这个属性的依赖,曾经增加依赖的办法 addSub,删除办法removeSub,还有一个notify 办法用来遍历更新它 subs 中的所有依赖,同时 Dep 类有一个动态属性 target 它用来示意以后的观察者,当后续进行依赖收集的时候能够将它增加到 dep.subs 中。
  3. 而后设计一个 observe 办法,这个办法接管的是传进来的 data,也就是options.data,外面会遍历data 中的每一个属性,并应用 Object.defineProperty() 来重写它的 getset,那么这外面呢能够应用 new Dep() 实例化一个 dep 对象,在 get 的时候调用其 addSub 办法增加以后的观察者 Dep.target 实现依赖收集,并且在 set 的时候调用 dep.notify 办法来告诉每一个依赖它的观察者进行更新
  4. 实现这些之后,咱们还须要一个 compile 办法来将 HTML 模版和数据联合起来。在这个办法中首先传入的是一个 node 节点,而后遍历它的所有子级,判断是否有 firstElmentChild,有的话则进行递归调用 compile 办法,没有firstElementChild 的话且该 child.innderHTML 用正则匹配满足有 /\{\{(.*)\}\}/ 项的话则示意有须要双向绑定的数据,那么就将用正则 new Reg('\\{\\{\\s*' + key + '\\s*\\}\\}', 'gm') 替换掉是其为 msg 变量。
  5. 实现变量替换的同时,还须要将 Dep.target 指向以后的这个 child,且调用一下this.opt.data[key],也就是为了触发这个数据的get 来对以后的 child 进行依赖收集,这样下次数据变动的时候就能告诉 child 进行视图更新了,不过在最初要记得将 Dep.target 指为 null 哦(其实在 Vue 中是有一个 targetStack 栈用来寄存 target 的指向的)
  6. 那么最初咱们只须要监听 documentDOMContentLoaded而后在回调函数中实例化这个 Vue 对象就能够了

coding :

须要留神的点:

  • childNodes会获取到所有的子节点以及文本节点(包含元素标签中的空白节点)
  • firstElementChild示意获取元素的第一个字元素节点,以此来辨别是不是元素节点,如果是的话则调用 compile 进行递归调用,否则用正则匹配
  • 这外面的正则真的不难,大家能够看一下

残缺代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>MVVM</title>
  </head>
  <body>
    <div id="app">
      <h3> 姓名 </h3>
      <p>{{name}}</p>
      <h3> 年龄 </h3>
      <p>{{age}}</p>
    </div>
  </body>
</html>
<script>
  document.addEventListener(
    "DOMContentLoaded",
    function () {let opt = { el: "#app", data: { name: "期待批改...", age: 20} };
      let vm = new Vue(opt);
      setTimeout(() => {opt.data.name = "jing";}, 2000);
    },
    false
  );
  class Vue {constructor(opt) {
      this.opt = opt;
      this.observer(opt.data);
      let root = document.querySelector(opt.el);
      this.compile(root);
    }
    observer(data) {Object.keys(data).forEach((key) => {let obv = new Dep();
        data["_" + key] = data[key];

        Object.defineProperty(data, key, {get() {Dep.target && obv.addSubNode(Dep.target);
            return data["_" + key];
          },
          set(newVal) {obv.update(newVal);
            data["_" + key] = newVal;
          },
        });
      });
    }
    compile(node) {[].forEach.call(node.childNodes, (child) => {if (!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)) {let key = RegExp.$1.trim();
          child.innerHTML = child.innerHTML.replace(new RegExp("\\{\\{\\s*" + key + "\\s*\\}\\}", "gm"),
            this.opt.data[key]
          );
          Dep.target = child;
          this.opt.data[key];
          Dep.target = null;
        } else if (child.firstElementChild) this.compile(child);
      });
    }
  }

  class Dep {constructor() {this.subNode = [];
    }
    addSubNode(node) {this.subNode.push(node);
    }
    update(newVal) {this.subNode.forEach((node) => {node.innerHTML = newVal;});
    }
  }
</script>

简化版 2

function update(){console.log('数据变动~~~ mock update view')
}
let obj = [1,2,3]
// 变异办法 push shift unshfit reverse sort splice pop
// Object.defineProperty
let oldProto = Array.prototype;
let proto = Object.create(oldProto); // 克隆了一分
['push','shift'].forEach(item=>{proto[item] = function(){update();
    oldProto[item].apply(this,arguments);
  }
})
function observer(value){ // proxy reflect
  if(Array.isArray(value)){
    // AOP
    return value.__proto__ = proto;
    // 重写 这个数组里的 push shift unshfit reverse sort splice pop
  }
  if(typeof value !== 'object'){return value;}
  for(let key in value){defineReactive(value,key,value[key]);
  }
}
function defineReactive(obj,key,value){observer(value); // 如果是对象 持续减少 getter 和 setter
  Object.defineProperty(obj,key,{get(){return value;},
    set(newValue){if(newValue !== value){observer(newValue);
            value = newValue;
            update();}
    }
  })
}
observer(obj); 
// AOP
// obj.name = {n:200}; // 数据变了 须要更新视图 深度监控
// obj.name.n = 100;
obj.push(123);
obj.push(456);
console.log(obj);

Array.prototype.filter()

Array.prototype.filter = function(callback, thisArg) {if (this == undefined) {throw new TypeError('this is null or not undefined');
  }
  if (typeof callback !== 'function') {throw new TypeError(callback + 'is not a function');
  }
  const res = [];
  // 让 O 成为回调函数的对象传递(强制转换对象)const O = Object(this);
  // >>>0 保障 len 为 number,且为正整数
  const len = O.length >>> 0;
  for (let i = 0; i < len; i++) {
    // 查看 i 是否在 O 的属性(会查看原型链)if (i in O) {
      // 回调函数调用传参
      if (callback.call(thisArg, O[i], i, O)) {res.push(O[i]);
      }
    }
  }
  return res;
}

正文完
 0