关于javascript:20道高频js手写题请查收

6次阅读

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

深克隆(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)); // 输入 3
console.log(getValue(array, "[0].a.b[0]", 12)); // 输入 1
console.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); // dark
console.log(data.theme); // dark

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

dark
dark

通过观察以上的输入后果可知,应用 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 
},{})
// 后果
-: 1
a: 8
c: 1
d: 4
e: 1
f: 4
g: 1
h: 2
i: 2
j: 4
k: 1
l: 3
m: 2
n: 1
q: 5
r: 1
s: 6
u: 1
w: 2
x: 1
z: 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 module
class 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()
  }
}

// reactive
function 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)
  }
}

// watcher
class 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.length
for(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.js
class 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;
}
正文完
 0