用正则写一个依据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  // trueNaN === 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}
// testvar 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.definePropertylet 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;}