深克隆(deepclone)

简略版:

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

局限性:

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

面试版:

/** * deep clone * @param  {[type]} parent object 须要进行克隆的对象 * @return {[type]}        深克隆后的对象 */const clone = parent => {  // 判断类型  const isType = (obj, type) => {    if (typeof obj !== "object") return false;    const typeString = Object.prototype.toString.call(obj);    let flag;    switch (type) {      case "Array":        flag = typeString === "[object Array]";        break;      case "Date":        flag = typeString === "[object Date]";        break;      case "RegExp":        flag = typeString === "[object RegExp]";        break;      default:        flag = false;    }    return flag;  };  // 解决正则  const getRegExp = re => {    var flags = "";    if (re.global) flags += "g";    if (re.ignoreCase) flags += "i";    if (re.multiline) flags += "m";    return flags;  };  // 保护两个贮存循环援用的数组  const parents = [];  const children = [];  const _clone = parent => {    if (parent === null) return null;    if (typeof parent !== "object") return parent;    let child, proto;    if (isType(parent, "Array")) {      // 对数组做非凡解决      child = [];    } else if (isType(parent, "RegExp")) {      // 对正则对象做非凡解决      child = new RegExp(parent.source, getRegExp(parent));      if (parent.lastIndex) child.lastIndex = parent.lastIndex;    } else if (isType(parent, "Date")) {      // 对Date对象做非凡解决      child = new Date(parent.getTime());    } else {      // 解决对象原型      proto = Object.getPrototypeOf(parent);      // 利用Object.create切断原型链      child = Object.create(proto);    }    // 解决循环援用    const index = parents.indexOf(parent);    if (index != -1) {      // 如果父数组存在本对象,阐明之前曾经被援用过,间接返回此对象      return children[index];    }    parents.push(parent);    children.push(child);    for (let i in parent) {      // 递归      child[i] = _clone(parent[i]);    }    return child;  };  return _clone(parent);};

局限性:

  1. 一些非凡状况没有解决: 例如Buffer对象、Promise、Set、Map
  2. 另外对于确保没有循环援用的对象,咱们能够省去对循环援用的非凡解决,因为这很耗费工夫
原理详解实现深克隆

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

一行代码能够解决:

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

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

查找字符串中呈现最多的字符和个数

例: abbcccddddd -> 字符最多的是d,呈现了5次

let str = "abcabcabcbbccccc";let num = 0;let char = ''; // 使其依照肯定的秩序排列str = str.split('').sort().join('');// "aaabbbbbcccccccc"// 定义正则表达式let re = /(\w)\1+/g;str.replace(re,($0,$1) => {    if(num < $0.length){        num = $0.length;        char = $1;            }});console.log(`字符最多的是${char},呈现了${num}次`);

实现公布订阅模式

简介:

公布订阅者模式,一种对象间一对多的依赖关系,但一个对象的状态产生扭转时,所依赖它的对象都将失去状态扭转的告诉。

次要的作用(长处):

  1. 广泛应用于异步编程中(代替了传递回调函数)
  2. 对象之间涣散耦合的编写代码

毛病:

  • 创立订阅者自身要耗费肯定的工夫和内存
  • 多个发布者和订阅者嵌套一起的时候,程序难以跟踪保护

实现的思路:

  • 创立一个对象(缓存列表)
  • on办法用来把回调函数fn都加到缓存列表中
  • emit 依据key值去执行对应缓存列表中的函数
  • off办法能够依据key值勾销订阅
class EventEmiter {  constructor() {    // 事件对象,寄存订阅的名字和事件    this._events = {}  }  // 订阅事件的办法  on(eventName,callback) {    if(!this._events) {      this._events = {}    }    // 合并之前订阅的cb    this._events[eventName] = [...(this._events[eventName] || []),callback]  }  // 触发事件的办法  emit(eventName, ...args) {    if(!this._events[eventName]) {      return    }    // 遍历执行所有订阅的事件    this._events[eventName].forEach(fn=>fn(...args))  }  off(eventName,cb) {    if(!this._events[eventName]) {      return    }    // 删除订阅的事件    this._events[eventName] = this._events[eventName].filter(fn=>fn != cb && fn.l != cb)  }  // 绑定一次 触发后将绑定的移除掉 再次触发掉  once(eventName,callback) {    const one = (...args)=>{      // 等callback执行结束在删除      callback(args)      this.off(eventName,one)    }    one.l = callback // 自定义属性    this.on(eventName,one)  }}

测试用例

let event = new EventEmiter()let login1 = function(...args) {  console.log('login success1', args)}let login2 = function(...args) {  console.log('login success2', args)}// event.on('login',login1)event.once('login',login2)event.off('login',login1) // 解除订阅event.emit('login', 1,2,3,4,5)event.emit('login', 6,7,8,9)event.emit('login', 10,11,12)  

公布订阅者模式和观察者模式的区别?

  • 公布/订阅模式是观察者模式的一种变形,两者区别在于,公布/订阅模式在观察者模式的根底上,在指标和观察者之间减少一个调度核心。
  • 观察者模式是由具体指标调度,比方当事件触发,Subject 就会去调用观察者的办法,所以观察者模式的订阅者与发布者之间是存在依赖的。
  • 公布/订阅模式由对立调度核心调用,因而发布者和订阅者不须要晓得对方的存在。

实现 getValue/setValue 函数来获取path对应的值

// 示例var object = { a: [{ b: { c: 3 } }] }; // path: 'a[0].b.c'var array = [{ a: { b: [1] } }]; // path: '[0].a.b[0]'function getValue(target, valuePath, defaultValue) {}console.log(getValue(object, "a[0].b.c", 0)); // 输入3console.log(getValue(array, "[0].a.b[0]", 12)); // 输入 1console.log(getValue(array, "[0].a.b[0].c", 12)); // 输入 12

实现

/** * 测试属性是否匹配 */export function testPropTypes(value, type, dev) {  const sEnums = ['number', 'string', 'boolean', 'undefined', 'function']; // NaN  const oEnums = ['Null', 'Object', 'Array', 'Date', 'RegExp', 'Error'];  const nEnums = [    '[object Number]',    '[object String]',    '[object Boolean]',    '[object Undefined]',    '[object Function]',    '[object Null]',    '[object Object]',    '[object Array]',    '[object Date]',    '[object RegExp]',    '[object Error]',  ];  const reg = new RegExp('\\[object (.*?)\\]');  // 齐全匹配模式,type应该传递相似格局[object Window] [object HTMLDocument] ...  if (reg.test(type)) {    // 排除nEnums的12种    if (~nEnums.indexOf(type)) {      if (dev === true) {        console.warn(value, 'The parameter type belongs to one of 12 types:number string boolean undefined Null Object Array Date RegExp function Error NaN');      }    }    if (Object.prototype.toString.call(value) === type) {      return true;    }    return false;  }}
const syncVarIterator = {  getter: function (obj, key, defaultValue) {    // 后果变量    const defaultResult = defaultValue === undefined ? undefined : defaultValue;    if (testPropTypes(obj, 'Object') === false && testPropTypes(obj, 'Array') === false) {      return defaultResult;    }    // 后果变量,临时指向obj持有的援用,后续将可能被一直的批改    let result = obj;    // 失去晓得值    try {      // 解析属性档次序列      const keyArr = key.split('.');      // 迭代obj对象属性      for (let i = 0; i < keyArr.length; i++) {        // 如果第 i 层属性存在对应的值则迭代该属性值        if (result[keyArr[i]] !== undefined) {          result = result[keyArr[i]];          // 如果不存在则返回未定义        } else {          return defaultResult;        }      }    } catch (e) {      return defaultResult;    }    // 返回获取的后果    return result;  },  setter: function (obj, key, val) {    // 如果不存在obj则返回未定义    if (testPropTypes(obj, 'Object') === false) {      return false;    }    // 后果变量,临时指向obj持有的援用,后续将可能被一直的批改    let result = obj;    try {      // 解析属性档次序列      const keyArr = key.split('.');      let i = 0;      // 迭代obj对象属性      for (; i < keyArr.length - 1; i++) {        // 如果第 i 层属性对应的值不存在,则定义为对象        if (result[keyArr[i]] === undefined) {          result[keyArr[i]] = {};        }        // 如果第 i 层属性对应的值不是对象(Object)的一个实例,则抛出谬误        if (!(result[keyArr[i]] instanceof Object)) {          throw new Error('obj.' + keyArr.splice(0, i + 1).join('.') + 'is not Object');        }        // 迭代该层属性值        result = result[keyArr[i]];      }      // 设置属性值      result[keyArr[i]] = val;      return true;    } catch (e) {      return false;    }  },};

应用promise来实现

创立 enhancedObject 函数

const enhancedObject = (target) =>  new Proxy(target, {    get(target, property) {      if (property in target) {        return target[property];      } else {        return searchFor(property, target); //理论应用时要对value值进行复位      }    },  });let value = null;function searchFor(property, target) {  for (const key of Object.keys(target)) {    if (typeof target[key] === "object") {      searchFor(property, target[key]);    } else if (typeof target[property] !== "undefined") {      value = target[property];      break;    }  }  return value;}

应用 enhancedObject 函数

const data = enhancedObject({  user: {    name: "test",    settings: {      theme: "dark",    },  },});console.log(data.user.settings.theme); // darkconsole.log(data.theme); // dark

以上代码运行后,控制台会输入以下代码:

darkdark
通过观察以上的输入后果可知,应用 enhancedObject 函数解决过的对象,咱们就能够不便地拜访一般对象外部的深层属性。

reduce用法汇总

语法

array.reduce(function(total, currentValue, currentIndex, arr), initialValue);/*  total: 必须。初始值, 或者计算完结后的返回值。  currentValue: 必须。以后元素。  currentIndex: 可选。以后元素的索引;                       arr: 可选。以后元素所属的数组对象。  initialValue: 可选。传递给函数的初始值,相当于total的初始值。*/
reduceRight() 该办法用法与reduce()其实是雷同的,只是遍历的程序相同,它是从数组的最初一项开始,向前遍历到第一项

1. 数组求和

const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num);// 设定初始值求和const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num, 10);  // 以10为初始值求和// 对象数组求和var result = [  { subject: 'math', score: 88 },  { subject: 'chinese', score: 95 },  { subject: 'english', score: 80 }];const sum = result.reduce((accumulator, cur) => accumulator + cur.score, 0); const sum = result.reduce((accumulator, cur) => accumulator + cur.score, -10);  // 总分扣除10分

2. 数组最大值

const a = [23,123,342,12];const max = a.reduce((pre,next)=>pre>cur?pre:cur,0); // 342

3. 数组转对象

var streams = [{name: '技术', id: 1}, {name: '设计', id: 2}];var obj = streams.reduce((accumulator, cur) => {accumulator[cur.id] = cur; return accumulator;}, {});

4. 扁平一个二维数组

var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];var res = arr.reduce((x, y) => x.concat(y), []);

5. 数组去重

实现的基本原理如下:① 初始化一个空数组② 将须要去重解决的数组中的第1项在初始化数组中查找,如果找不到(空数组中必定找不到),就将该项增加到初始化数组中③ 将须要去重解决的数组中的第2项在初始化数组中查找,如果找不到,就将该项持续增加到初始化数组中④ ……⑤ 将须要去重解决的数组中的第n项在初始化数组中查找,如果找不到,就将该项持续增加到初始化数组中⑥ 将这个初始化数组返回
var newArr = arr.reduce(function (prev, cur) {    prev.indexOf(cur) === -1 && prev.push(cur);    return prev;},[]);

6. 对象数组去重

const dedup = (data, getKey = () => { }) => {    const dateMap = data.reduce((pre, cur) => {        const key = getKey(cur)        if (!pre[key]) {            pre[key] = cur        }        return pre    }, {})    return Object.values(dateMap)}

7. 求字符串中字母呈现的次数

const str = 'sfhjasfjgfasjuwqrqadqeiqsajsdaiwqdaklldflas-cmxzmnha';const res = str.split('').reduce((pre,next)=>{ pre[next] ? pre[next]++ : pre[next] = 1 return pre },{})
// 后果-: 1a: 8c: 1d: 4e: 1f: 4g: 1h: 2i: 2j: 4k: 1l: 3m: 2n: 1q: 5r: 1s: 6u: 1w: 2x: 1z: 1

8. compose函数

redux compose 源码实现
function compose(...funs) {    if (funs.length === 0) {        return arg => arg;    }    if (funs.length === 1) {       return funs[0];    }    return funs.reduce((a, b) => (...arg) => a(b(...arg)))}

Promise实现

基于Promise封装Ajax

  • 返回一个新的Promise实例
  • 创立HMLHttpRequest异步对象
  • 调用open办法,关上url,与服务器建设链接(发送前的一些解决)
  • 监听Ajax状态信息
  • 如果xhr.readyState == 4(示意服务器响应实现,能够获取应用服务器的响应了)

    • xhr.status == 200,返回resolve状态
    • xhr.status == 404,返回reject状态
  • xhr.readyState !== 4,把申请主体的信息基于send发送给服务器
function ajax(url) {  return new Promise((resolve, reject) => {    let xhr = new XMLHttpRequest()    xhr.open('get', url)    xhr.onreadystatechange = () => {      if (xhr.readyState == 4) {        if (xhr.status >= 200 && xhr.status <= 300) {          resolve(JSON.parse(xhr.responseText))        } else {          reject('申请出错')        }      }    }    xhr.send()  //发送hppt申请  })}let url = '/data.json'ajax(url).then(res => console.log(res))  .catch(reason => console.log(reason))

参考:前端手写面试题具体解答

实现Vue reactive响应式

// Dep moduleclass Dep {  static stack = []  static target = null  deps = null  constructor() {    this.deps = new Set()  }  depend() {    if (Dep.target) {      this.deps.add(Dep.target)    }  }  notify() {    this.deps.forEach(w => w.update())  }  static pushTarget(t) {    if (this.target) {      this.stack.push(this.target)    }    this.target = t  }  static popTarget() {    this.target = this.stack.pop()  }}// reactivefunction reactive(o) {  if (o && typeof o === 'object') {    Object.keys(o).forEach(k => {      defineReactive(o, k, o[k])    })  }  return o}function defineReactive(obj, k, val) {  let dep = new Dep()  Object.defineProperty(obj, k, {    get() {      dep.depend()      return val    },    set(newVal) {      val = newVal      dep.notify()    }  })  if (val && typeof val === 'object') {    reactive(val)  }}// watcherclass Watcher {  constructor(effect) {    this.effect = effect    this.update()  }  update() {    Dep.pushTarget(this)    this.value = this.effect()    Dep.popTarget()    return this.value  }}// 测试代码const data = reactive({  msg: 'aaa'})new Watcher(() => {  console.log('===> effect', data.msg);})setTimeout(() => {  data.msg = 'hello'}, 1000)

实现千位分隔符

// 保留三位小数parseToMoney(1234.56); // return '1,234.56'parseToMoney(123456789); // return '123,456,789'parseToMoney(1087654.321); // return '1,087,654.321'
function parseToMoney(num) {  num = parseFloat(num.toFixed(3));  let [integer, decimal] = String.prototype.split.call(num, '.');  integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');  return integer + '.' + (decimal ? decimal : '');}

正则表达式(使用了正则的前向申明和反前向申明):

function parseToMoney(str){    // 仅仅对地位进行匹配    let re = /(?=(?!\b)(\d{3})+$)/g;    return str.replace(re,','); }

判断是否是电话号码

function isPhone(tel) {    var regx = /^1[34578]\d{9}$/;    return regx.test(tel);}

实现模板字符串解析性能

let template = '我是{{name}},年龄{{age}},性别{{sex}}';let data = {  name: '姓名',  age: 18}render(template, data); // 我是姓名,年龄18,性别undefined
function render(template, data) {  const reg = /\{\{(\w+)\}\}/; // 模板字符串正则  if (reg.test(template)) { // 判断模板里是否有模板字符串    const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染    return render(template, data); // 递归的渲染并返回渲染后的构造  }  return template; // 如果模板没有模板字符串间接返回}

实现一个迭代器生成函数

ES6对迭代器的实现

JS原生的汇合类型数据结构,只有Array(数组)和Object(对象);而ES6中,又新增了MapSet。四种数据结构各自有着本人特地的外部实现,但咱们仍期待以同样的一套规定去遍历它们,所以ES6在推出新数据结构的同时也推出了一套 对立的接口机制 ——迭代器(Iterator)。

ES6约定,任何数据结构只有具备Symbol.iterator属性(这个属性就是Iterator的具体实现,它实质上是以后数据结构默认的迭代器生成函数),就能够被遍历——精确地说,是被for...of...循环和迭代器的next办法遍历。 事实上,for...of...的背地正是对next办法的重复调用。

在ES6中,针对ArrayMapSetStringTypedArray、函数的 arguments 对象、NodeList 对象这些原生的数据结构都能够通过for...of...进行遍历。原理都是一样的,此处咱们拿最简略的数组进行举例,当咱们用for...of...遍历数组时:

const arr = [1, 2, 3]const len = arr.lengthfor(item of arr) {   console.log(`以后元素是${item}`)}
之所以可能按程序一次一次地拿到数组里的每一个成员,是因为咱们借助数组的Symbol.iterator生成了它对应的迭代器对象,通过重复调用迭代器对象的next办法拜访了数组成员,像这样:
const arr = [1, 2, 3]// 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()// 对迭代器对象执行next,就能一一拜访汇合的成员iterator.next()iterator.next()iterator.next()

丢进控制台,咱们能够看到next每次会按程序帮咱们拜访一个汇合成员:

for...of...做的事件,根本等价于上面这通操作:
// 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()// 初始化一个迭代后果let now = { done: false }// 循环往外迭代成员while(!now.done) {    now = iterator.next()    if(!now.done) {        console.log(`当初遍历到了${now.value}`)    }}
能够看出,for...of...其实就是iterator循环调用换了种写法。在ES6中咱们之所以可能开心地用for...of...遍历各种各种的汇合,全靠迭代器模式在背地给力。

ps:此处举荐浏览迭代协定 (opens new window),置信大家读过后会对迭代器在ES6中的实现有更深的了解。

实现一个JSON.stringify

JSON.stringify(value[, replacer [, space]]):
  • Boolean | Number| String类型会主动转换成对应的原始值。
  • undefined、任意函数以及symbol,会被疏忽(呈现在非数组对象的属性值中时),或者被转换成 null(呈现在数组中时)。
  • 不可枚举的属性会被疏忽如果一个对象的属性值通过某种间接的形式指回该对象自身,即循环援用,属性也会被疏忽
  • 如果一个对象的属性值通过某种间接的形式指回该对象自身,即循环援用,属性也会被疏忽
function jsonStringify(obj) {    let type = typeof obj;    if (type !== "object") {        if (/string|undefined|function/.test(type)) {            obj = '"' + obj + '"';        }        return String(obj);    } else {        let json = []        let arr = Array.isArray(obj)        for (let k in obj) {            let v = obj[k];            let type = typeof v;            if (/string|undefined|function/.test(type)) {                v = '"' + v + '"';            } else if (type === "object") {                v = jsonStringify(v);            }            json.push((arr ? "" : '"' + k + '":') + String(v));        }        return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")    }}jsonStringify({x : 5}) // "{"x":5}"jsonStringify([1, "false", false]) // "[1,"false",false]"jsonStringify({b: undefined}) // "{"b":"undefined"}"

对象扁平化

function objectFlat(obj = {}) {  const res = {}  function flat(item, preKey = '') {    Object.entries(item).forEach(([key, val]) => {      const newKey = preKey ? `${preKey}.${key}` : key      if (val && typeof val === 'object') {        flat(val, newKey)      } else {        res[newKey] = val      }    })  }  flat(obj)  return res}// 测试const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } }console.log(objectFlat(source));

实现一个迷你版的vue

入口

// js/vue.jsclass Vue {  constructor (options) {    // 1. 通过属性保留选项的数据    this.$options = options || {}    this.$data = options.data || {}    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el    // 2. 把data中的成员转换成getter和setter,注入到vue实例中    this._proxyData(this.$data)    // 3. 调用observer对象,监听数据的变动    new Observer(this.$data)    // 4. 调用compiler对象,解析指令和差值表达式    new Compiler(this)  }  _proxyData (data) {    // 遍历data中的所有属性    Object.keys(data).forEach(key => {      // 把data的属性注入到vue实例中      Object.defineProperty(this, key, {        enumerable: true,        configurable: true,        get () {          return data[key]        },        set (newValue) {          if (newValue === data[key]) {            return          }          data[key] = newValue        }      })    })  }}

实现Dep

class Dep {  constructor () {    // 存储所有的观察者    this.subs = []  }  // 增加观察者  addSub (sub) {    if (sub && sub.update) {      this.subs.push(sub)    }  }  // 发送告诉  notify () {    this.subs.forEach(sub => {      sub.update()    })  }}

实现watcher

class Watcher {  constructor (vm, key, cb) {    this.vm = vm    // data中的属性名称    this.key = key    // 回调函数负责更新视图    this.cb = cb    // 把watcher对象记录到Dep类的动态属性target    Dep.target = this    // 触发get办法,在get办法中会调用addSub    this.oldValue = vm[key]    Dep.target = null  }  // 当数据发生变化的时候更新视图  update () {    let newValue = this.vm[this.key]    if (this.oldValue === newValue) {      return    }    this.cb(newValue)  }}

实现compiler

class Compiler {  constructor (vm) {    this.el = vm.$el    this.vm = vm    this.compile(this.el)  }  // 编译模板,解决文本节点和元素节点  compile (el) {    let childNodes = el.childNodes    Array.from(childNodes).forEach(node => {      // 解决文本节点      if (this.isTextNode(node)) {        this.compileText(node)      } else if (this.isElementNode(node)) {        // 解决元素节点        this.compileElement(node)      }      // 判断node节点,是否有子节点,如果有子节点,要递归调用compile      if (node.childNodes && node.childNodes.length) {        this.compile(node)      }    })  }  // 编译元素节点,解决指令  compileElement (node) {    // console.log(node.attributes)    // 遍历所有的属性节点    Array.from(node.attributes).forEach(attr => {      // 判断是否是指令      let attrName = attr.name      if (this.isDirective(attrName)) {        // v-text --> text        attrName = attrName.substr(2)        let key = attr.value        this.update(node, key, attrName)      }    })  }  update (node, key, attrName) {    let updateFn = this[attrName + 'Updater']    updateFn && updateFn.call(this, node, this.vm[key], key)  }  // 解决 v-text 指令  textUpdater (node, value, key) {    node.textContent = value    new Watcher(this.vm, key, (newValue) => {      node.textContent = newValue    })  }  // v-model  modelUpdater (node, value, key) {    node.value = value    new Watcher(this.vm, key, (newValue) => {      node.value = newValue    })    // 双向绑定    node.addEventListener('input', () => {      this.vm[key] = node.value    })  }  // 编译文本节点,解决差值表达式  compileText (node) {    // console.dir(node)    // {{  msg }}    let reg = /\{\{(.+?)\}\}/    let value = node.textContent    if (reg.test(value)) {      let key = RegExp.$1.trim()      node.textContent = value.replace(reg, this.vm[key])      // 创立watcher对象,当数据扭转更新视图      new Watcher(this.vm, key, (newValue) => {        node.textContent = newValue      })    }  }  // 判断元素属性是否是指令  isDirective (attrName) {    return attrName.startsWith('v-')  }  // 判断节点是否是文本节点  isTextNode (node) {    return node.nodeType === 3  }  // 判断节点是否是元素节点  isElementNode (node) {    return node.nodeType === 1  }}

实现Observer

class Observer {  constructor (data) {    this.walk(data)  }  walk (data) {    // 1. 判断data是否是对象    if (!data || typeof data !== 'object') {      return    }    // 2. 遍历data对象的所有属性    Object.keys(data).forEach(key => {      this.defineReactive(data, key, data[key])    })  }  defineReactive (obj, key, val) {    let that = this    // 负责收集依赖,并发送告诉    let dep = new Dep()    // 如果val是对象,把val外部的属性转换成响应式数据    this.walk(val)    Object.defineProperty(obj, key, {      enumerable: true,      configurable: true,      get () {        // 收集依赖        Dep.target && dep.addSub(Dep.target)        return val      },      set (newValue) {        if (newValue === val) {          return        }        val = newValue        that.walk(newValue)        // 发送告诉        dep.notify()      }    })  }}

应用

<!DOCTYPE html><html lang="cn"><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>Mini Vue</title></head><body>  <div id="app">    <h1>差值表达式</h1>    <h3>{{ msg }}</h3>    <h3>{{ count }}</h3>    <h1>v-text</h1>    <div v-text="msg"></div>    <h1>v-model</h1>    <input type="text" v-model="msg">    <input type="text" v-model="count">  </div>  <script src="./js/dep.js"></script>  <script src="./js/watcher.js"></script>  <script src="./js/compiler.js"></script>  <script src="./js/observer.js"></script>  <script src="./js/vue.js"></script>  <script>    let vm = new Vue({      el: '#app',      data: {        msg: 'Hello Vue',        count: 100,        person: { name: 'zs' }      }    })    console.log(vm.msg)    // vm.msg = { test: 'Hello' }    vm.test = 'abc'  </script></body></html>

字符串最长的不反复子串

题目形容

给定一个字符串 s ,请你找出其中不含有反复字符的 最长子串 的长度。示例 1:输出: s = "abcabcbb"输入: 3解释: 因为无反复字符的最长子串是 "abc",所以其长度为 3。示例 2:输出: s = "bbbbb"输入: 1解释: 因为无反复字符的最长子串是 "b",所以其长度为 1。示例 3:输出: s = "pwwkew"输入: 3解释: 因为无反复字符的最长子串是 "wke",所以其长度为 3。     请留神,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。示例 4:输出: s = ""输入: 0

答案

const lengthOfLongestSubstring = function (s) {  if (s.length === 0) {    return 0;  }  let left = 0;  let right = 1;  let max = 0;  while (right <= s.length) {    let lr = s.slice(left, right);    const index = lr.indexOf(s[right]);    if (index > -1) {      left = index + left + 1;    } else {      lr = s.slice(left, right + 1);      max = Math.max(max, lr.length);    }    right++;  }  return max;};

解析 URL Params 为对象

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';parseParam(url)/* 后果{ user: 'anonymous',  id: [ 123, 456 ], // 反复呈现的 key 要组装成数组,能被转成数字的就转成数字类型  city: '北京', // 中文需解码  enabled: true, // 未指定值得 key 约定为 true}*/
function parseParam(url) {  const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 前面的字符串取出来  const paramsArr = paramsStr.split('&'); // 将字符串以 & 宰割后存到数组中  let paramsObj = {};  // 将 params 存到对象中  paramsArr.forEach(param => {    if (/=/.test(param)) { // 解决有 value 的参数      let [key, val] = param.split('='); // 宰割 key 和 value      val = decodeURIComponent(val); // 解码      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字      if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则增加一个值        paramsObj[key] = [].concat(paramsObj[key], val);      } else { // 如果对象没有这个 key,创立 key 并设置值        paramsObj[key] = val;      }    } else { // 解决没有 value 的参数      paramsObj[param] = true;    }  })  return paramsObj;}

手写节流函数

函数节流是指规定一个单位工夫,在这个单位工夫内,只能有一次触发事件的回调函数执行,如果在同一个单位工夫内某事件被触发屡次,只有一次能失效。节流能够应用在 scroll 函数的事件监听上,通过事件节流来升高事件调用的频率。

// 函数节流的实现;function throttle(fn, delay) {  let curTime = Date.now();  return function() {    let context = this,        args = arguments,        nowTime = Date.now();    // 如果两次工夫距离超过了指定工夫,则执行函数。    if (nowTime - curTime >= delay) {      curTime = Date.now();      return fn.apply(context, args);    }  };}

实现一下hash路由

根底的html代码:

<html>  <style>    html, body {      margin: 0;      height: 100%;    }    ul {      list-style: none;      margin: 0;      padding: 0;      display: flex;      justify-content: center;    }    .box {      width: 100%;      height: 100%;      background-color: red;    }  </style>  <body>  <ul>    <li>      <a href="#red">红色</a>    </li>    <li>      <a href="#green">绿色</a>    </li>    <li>      <a href="#purple">紫色</a>    </li>  </ul>  </body></html>

简略实现:

<script>  const box = document.getElementsByClassName('box')[0];  const hash = location.hash  window.onhashchange = function (e) {    const color = hash.slice(1)    box.style.background = color  }</script>

封装成一个class:

<script>  const box = document.getElementsByClassName('box')[0];  const hash = location.hash  class HashRouter {    constructor (hashStr, cb) {      this.hashStr = hashStr      this.cb = cb      this.watchHash()      this.watch = this.watchHash.bind(this)      window.addEventListener('hashchange', this.watch)    }    watchHash () {      let hash = window.location.hash.slice(1)      this.hashStr = hash      this.cb(hash)    }  }  new HashRouter('red', (color) => {    box.style.background = color  })</script>

对象数组列表转成树形构造(解决菜单)

[    {        id: 1,        text: '节点1',        parentId: 0 //这里用0示意为顶级节点    },    {        id: 2,        text: '节点1_1',        parentId: 1 //通过这个字段来确定子父级    }    ...]转成[    {        id: 1,        text: '节点1',        parentId: 0,        children: [            {                id:2,                text: '节点1_1',                parentId:1            }        ]    }]

实现代码如下:

function listToTree(data) {  let temp = {};  let treeData = [];  for (let i = 0; i < data.length; i++) {    temp[data[i].id] = data[i];  }  for (let i in temp) {    if (+temp[i].parentId != 0) {      if (!temp[temp[i].parentId].children) {        temp[temp[i].parentId].children = [];      }      temp[temp[i].parentId].children.push(temp[i]);    } else {      treeData.push(temp[i]);    }  }  return treeData;}