ES6 常用工具库

【3】林三心 -Diff算法核心原理 !!!【1】thWinterSun -GIT面试!!!
【2】FrankKai-GIT面试!!!
【1】阶乘递归函数运行的过程;【2】颜海镜 - 拷贝; + 【2】掘金 - 深拷贝
JavaScript闭包 !
this 作用域 !!!
this、apply、call、bind
Typescript 泛型 !!【掘】图解DIFF
【掘】自述vue双向绑定 ---- 原理 -博客园
【掘】响应式 Dep.target = null;
【掘】vue与react的个人体会;
【掘】会用 ES6;
【掘】forEach、filter、map 区别;
【掘】3-5前端面经;
【CSS】张鑫旭 -回流与重绘 !!!

【1】而实现 mvvm 的双向绑定有以下几点:

  • 1、vue会将 data 初始化为一个 Observer 监听器,并会用 Object.defineProperty() 进行数据响应式的绑定,而通过递归遍历(new Observer(val) -递归子属性), 将对象中的每个值的属性转换成 getter 和 setter的形式来侦测变化,而 data 中的每个 key,都拥有一个独立的消息订阅器 Dep(依赖收集器),用来收集订阅者,是一个封装好的 Dep 类,专门管理收集到的依赖;
  • 2、在读取对象属性时,会触发 get() 函数,同时判断是否要添加订阅者,需要则向消息订阅器 Dep添加当前的依赖,收集到的当前的订阅者,也就是 Watcher,最后把Watcher订阅对象到数组里(Dep.target.addDep());
  • 3、而在 mount 挂载函数时会初始化一个 Watcher(订阅者),而它会在自身实例化时往 消息订阅器Dep 中添加自己,也就是将当前收集器的目标 Dep.target 指向自己,同时触发 get()函数,从而将当前的 Wathcer 与 Dep 关联起来,完成依赖收集
  • 4、在 data 值发生变更时,触发set,调用发布订阅中心的notify,触发了依赖收集器 Dep 类,来遍历当前这个数据的订阅数组,执行里面所有的watcher,触发 Watcher.update 从而进行视图更新
const Observer = function(data) {
  for (let key in data) {  //===> 循环修改为data中每个属性添加 get set
    defineReactive(data, key);
  }
}

const defineReactive = function(obj, key) {
  const dep = new Dep();  // 局部变量dep,用于 get set 内部调用
  // 获取当前值
  let val = obj[key];
  Object.defineProperty(obj, key, {
    enumerable: true,     // 设置当前描述属性为可被循环
    configurable: true,  // 设置当前描述属性可被修改
    get() {
      console.log('in get');
      // 调用依赖收集器中的addSub,用于收集当前属性与Watcher中的依赖关系
      dep.depend();
      return val;
    },
    set(newVal) {
      if (newVal === val) return;
      val = newVal;
      //===> 当值发生变更时,通知依赖收集器,更新每个需要更新的Watcher
      dep.notify();  // 这里每个需要更新通过什么断定?dep.subs
    }
  });
}

const observe = function(data) { return new Observer(data); }

const Vue = function(options) {
  const self = this;
  // 将 data 赋值给 this._data,源码这部分用的 Proxy 所以我们用最简单的方式临时实现
  if (options && typeof options.data === 'function') {
    this._data = options.data.apply(this);
  }
  //===> 挂载函数(初始化)
  this.mount = function() {
    new Watcher(self, self.render);
  }
  // 渲染函数
  this.render = function() {
    with(self) {
      _data.text;
    }
  }
  observe(this._data);    // 监听 this._data
}

const Watcher = function(vm, fn) {
  const self = this;
  this.vm = vm;
  Dep.target = this;  // 实例化时(初始化),将当前收集器的目标 Dep.target 指向当前 watcher(自己),缓存自己
  this.addDep = function(dep) {  //===> 向 Dep 方法添加当前 Wathcer 到 订阅者数组内,我这个Watcher要被塞到Dep里去了
    dep.addSub(self);
  }
  this.update = function() {    //===> 更新方法,用于触发 vm._render,Dep通知我更新
    console.log('in watcher update');
    fn();
  }
  // 将自己添加到订阅器的操作,这里会首次调用 vm._render,从而触发 text 的 get
  // 从而将当前的 Wathcer 与 Dep 关联起来
  this.value = fn();
  //===> 这里null 是清空了 Dep.target,为了防止notify触发时,不停的绑定Watcher与Dep,造成代码死循环
  Dep.target = null; // // 添加完毕,重置,释放自己
}

const Dep = function() {
  const self = this;
  // 收集目标
  this.target = null;
  // 存储收集器中需要通知的Watcher
  this.subs = [];
  //===> 当有目标时,绑定 Dep 与 Wathcer 的关系
  this.depend = function() {
    if (Dep.target) {  // 为了让Watcher初始化进行触发,判断是否需要添加订阅者(初始化)
      // 可以直接写 self.addSub(Dep.target),
      // 没有这么写因为想还原源码的过程。
      Dep.target.addDep(self);
    }
  }
  this.addSub = function(watcher) { //===> 为当前收集器添加 Watcher
    self.subs.push(watcher);
  }
  this.notify = function() {  //===> 通知收集器中所的所有Wathcer,调用其update方法
    for (let i = 0; i < self.subs.length; i += 1) { // 遍历 订阅者
      self.subs[i].update();
    }
  }
}

const vue = new Vue({
  data() { return { text: 'hello world' }; }
})

vue.mount(); // in get
vue._data.text = '123'; // in watcher update /n in get

在这里插入图片描述

  • Observer 负责将数据转换成getter/setter形式; Dep 负责管理数据的依赖列表,是一个发布订阅模式,上游对接 Observer,下游对接 Watcher,而 Watcher 是实际上的数据依赖,负责将数据的变化转发到外界(渲染、回调);
  • 首先将 data 传入Observer 转成 getter/setter形式;当Watcher实例读取数据时,会触发getter,被收集到Dep仓库中;当数据更新时,触发setter,通知Dep仓库中的所有Watcher实例更新,Watcher实例负责通知外界;
  • vue 的diff 算法是 深度优先遍历 还是 广度优先算法? - 在 sameVnode() 会判断是否是相同的节点,然后在 patchVnode() 过程中会调用 updateChildren,所以 vue 的 diff 算法是个 深度优先算法
  • Vue3 - API !!
    VUE

- 正则 match() 方法 可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配,返回一个数组;
在这里插入图片描述

方法:

  • - split(): 就是将一字符串以特定的字符分割成多个字符串,join -方法将数组作为字符串返回;
  • - indexOf():可以判断一个元素是否在数组中存在,或者判断一个字符是否在字符串中存在,如果存在返回该元素或字符在 “第一次出现的” 位置的 索引(下标) 不存在返回 -1; exmyth 链接 !!!
  • - hasOwnProperty(): 检测一个属性是否是对象的自有属性。也是 Javascript 中唯一一个处理对象属性 而不会 往上遍历原型链的- JSON.parse(JSON.stringify()) !!!
  • - filter(): 和 map() 类似,它也接收一个函数。和 map() 不同的是,它把传入的函数依次作用于每个元素,然后根据返回值是 true 还是 false 决定 保留 还是 丢弃 该元素- 多个字段同时重复 !!!
// 切割域名,获取域名后的地址路径
private getRouterUrl() {
  const url = document.location.toString();
  const arrUrl = url.split("//");           // ['https:', 'www.baidu.com/index']
  const start = arrUrl[1].indexOf("/");     // 下标位置,14
  let relUrl = arrUrl[1].substring(start); // stop省略,截取从start开始到结尾的所有字符

  // 判断是否有传参,split 切割 ?后的参数
  if (relUrl.indexOf("?") != -1) {
    relUrl = relUrl.split("?")[0];
  }
  // console.log(arrUrl);
  return relUrl
}

var obj1 = {
  name: '孙悟空',
  age: 500
};
// 使用for...in遍历对象时,不仅会遍历自身属性,也会遍历原型链上的属性
for(key in obj1) {
  if(obj1.hasOwnProperty(key)){
    console.log(key)  // 打印出: name  age 
  }
}

// trim() 函数去掉字符串首尾空白字符
let arr = ['A', '', 'B', null, undefined, 'C', '  '];
let r = arr.filter((el, index, self) => {
    return el && el.trim(); // 注意:IE9 以下的版本没有 trim()方法
});
arr; // ['A', 'B', 'C']

// indexOf() 总是返回第一个元素的位置,后续的重复元素位置与indexOf返回的位置不相等,
// 因此被filter滤掉了,所以重复的元素仅会保留第一个位置的元素
let r,
  arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];

  r = arr.filter((el, index, self) => {
    return self.indexOf(el) === index;
  });
 
alert(r.toString());

// reduce() 还可以将多维数组转化为一维
let newobj = {};
person = person.reduce((cur, next) => {
  newobj[next.id] ? "" : newobj[next.id] = true && cur.push(next);
  return cur;
  
}, []) //设置cur默认类型为数组,并且初始值为空的数组
console.log(person);

// '字符串' 反转
arr.split('').reverse().join('');

【1】- 递归

思想: 在这个过程中充分反应了 “传递”(询问)“回归”(反馈)的思想。同时 递归算法运行效率低、性能消耗大,递归深度较大时慎用(等不到结果),还可能会导致栈溢出。

书写“套路”:

  • 1、找到终止条件,写给 if;
  • 2、找到函数的等价关系式,写给 return;
// 每隔一秒输出 1,2,3,4,5
const recursiveTest = function(){
   console.time("递归时间")
    const test = function (i) {
        setTimeout(function () {
            i = i + 1
            console.log('递归输出:' + i)
            if(i < 5){
                test(i)
            } else {
                console.timeEnd("递归时间")
            }
        }, 1000)
    }
    test(0)
}
recursiveTest();

// 递归求和
function factorial(num) {
   // 满足条件退出递归;
   if(num <= 2) return num;
   // 将递推关系的结构转换为递归体;
   return num * factorial(num - 1); 
}

// 循环算法,解决了 递归 消耗系统性能的问题,可以计算任意数值
function fn(n) {
    // 满足条件退出递归
    if(n === 1 || n === 2) return n;
    let left = 1; // 左边的数据
    let right = 2; // 右边的数据
    let sum = 0;
    
    for(var i = 3 ; i <= n ; i++) { // 循环从第3项开始
        sum = left + right; // 计算前一次左右数据的和
        left = right; // 把前一次的right赋值给下一次的left
        right = sum; // 把前一次的和赋值给下一次的right
    }
    return sum;
}

// 递归实现,https://juejin.cn/post/6844903591090601997#heading-0
let result = '';
const recursion = (cityData, id) => {
  // cityData数据为空的时候直接返回
  if (!cityData || !cityData.length) return;
  // 常规循环cityData
  for (let i = 0, len = cityData.length; i < len; i++) {
    const childs = cityData[i].children;
    
    // 如果匹配到id的话,就是我们要的结果
    if (cityData[i].id === id) result = cityData[i].name
    // 如果还有子节点,执行递归
    if(childs && childs.length > 0){
      recursion(childs, id);
    }
  }
  return result
};

const r = recursion(cityData, 11112);
console.log(r) // 灵芝

出栈与入栈
递归


【2】深浅拷贝-最根本的区别在于 是否真正获取一个对象的复制实体,而不是引用

浅拷贝:一个值 赋给 另一个值,当原先的值不改变地址的情况下改变数据,另一个值跟着变;(会引用同一块内存区域,拷贝的深度不够
深拷贝:一个值 赋给 另一个值,当原先的值不改变地址的情况下改变数据,另一个值不变;

- 记区别:

  • 基本数据类型的特点:直接存储在 “栈(stack)中” 的数据
  • 引用数据类型的特点:存储的是该对象 “在栈中引用”,真实的数据存放在 “堆内存里”
  • 引用类型在赋值时,赋的值地址;(地址类型,先在堆中申请空间 放数据 ,再把堆区的地址 给变量) 【— 深入理解】
  • ||==》
  • 浅拷贝(shallowCopy)只是增加了一个指针指向 “已存在” 的内存地址!!!
  • 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个“新的内存”!!!(获得了实体)

注意 : 原先的值不改变地址的情况下改变数据,如果是改了地址的改变,那将用另一个值的改变与否 判断 深拷贝 浅拷贝 毫无意义。

  • - Object.getOwnPropertyNames() - 可以拿到不可遍历 属性名: [‘name’, ‘age’, ‘null_keys’, ‘arr’, ‘foo’];
  • - Object.hasOwnProperty() - 返回一个布尔值,判断对象是否包含特定的自身(非继承)属性;(忽略“继承属性”)
  • - Object.constructor() - 返回对创建此对象的数组函数的引用;
  • - Object.instanceof() - 运算符用于检测 构造函数的 prototype 属性 是否出现在某个实例对象的原型链上;
  • 数组的 typeof 也是 ‘object’,所以用 Array.isArray(obj)
let testObj = {
  name: "張三",
  age: 20,
  null_keys: null,
  arr: [1, 2, 3, 4],
  foo() {
    console.log("name")
  }
};

// 递归深拷贝 不考虑集合映射
function deepCloneFoo(obj, newsObj = {}) {
  // 【1】遍历拿到所有的键名 getOwnPropertyNames()
  Object.getOwnPropertyNames(obj).forEach(key => {
    // console.log(obj[key]);
    // 【2】判断是否为复杂数据类型(引用类型) 以及防止null
    if (typeof obj[key] == "object" && obj[key]) {
      // 【3】判断原型 数组给初始化空数组
      if (obj[key] instanceof Array) {
        newsObj[key] = [];
      } else {
        newsObj[key] = {};  // 对象给初始化空对象
      }
      cloneFoo(obj[key], newsObj[key]) // 递归函数
      
    } else if (obj[key] instanceof Function) { // 【4】检测 Function 是否存在于参数obj的原型链上
        newsObj[key] = obj[key]
        
    } else {
      newsObj[key] = obj[key];  // 【5】一般数据类型直接赋值
    }
  })
  return newsObj
}

function deepClone(origin) {
  if(origin === null) return null 
  if(typeof origin !== 'object') return origin;
  if(origin.constructor === Date) return new Date(origin); 
  // 接受两个参数,origin 是原对象
  var _target = origin.constructor(); // 保持继承链
  
  // 循环 origin
  for(var key in origin) {
    // 不遍历其原型链上的属性
    if (origin.hasOwnProperty(key)) {
    	// 如果 origin[key] 是一个引用类型的值,则进入递归逻辑
      if (typeof origin[key] === 'object' && origin[key] !== null) {
        // 进入递归,此时原始值就是 origin[key],被赋值的对象是 _target[key]
        // 注意,上述第一次声明的 _target 将会贯穿整个递归,后续所有的赋值,都将会被 return 到 _target
        _target[key] = deepClone(origin[key])
        
      } else {
        // 如果不是对象或数组,则进入此逻辑,直接赋值给 _target[key]
        _target[key] = origin[key]
      }
    }
  }
  
  return _target     // for...in 循环结束后,return 当前上下文的 _target 值
}

【3】map() 数组 去重

  • - Array.filter() - 可以看成是一个过滤函数,返回符合条件的元素的数组;
  • filter() 方法传入一个回调函数,这个回调函数会携带一个参数,参数为当前迭代的项(我们叫它 item )。回调函数 返回 true 的项会保留在数组中,返回 false 的项会被过滤出数组。
  • - Array.has() - 在数组中寻找是否包含有匹配的值,如果存在则跳过;是作用于它的后代元素中
  • ES6在原型上新增了 Array.Includes() 方法(找到返true,否则返false),取代传统 IndexOf() 查找字符串的方法(返回某个指定的字符串值在字符串中首次出现的位置,没找到返-1);
  • - Array.indexOf() - 如果当前数组的 第 i 项 在当前数组中第一次出现的位置不是 i;那么表示 第 i 项 是重复的,忽略掉。否则存入结果数组。
// parseInt() 默认有两个参数,第二个参数是进制数
function returnInt(element){
  return parseInt(element, 10);
}
["1", "2", "3"].map(returnInt); // 1,2,3

let arr = [1, 2, 3, 4, 5];
let arr2 = arr.map(num => num * 2).filter(num => num > 5); // [6, 8, 10]

// 循环对数组中的元素调用callback 函数, 如果返回true 保留,如果返回false 过滤掉, 返回新数组,老数组不变
let arr_unique = arr.filter(function(item, idx, array){
  return idx === array.indexOf(item);
})

【4】reduce() - 会把所有元素按照聚合函数聚合成一个结果

  • reduce() - 按属性分组对象

    1、previousValue(pre): “累计器” — 累加器累加回调函数的返回值,Array类型(,[] 初始)。(上一次调用回调返回的值,或者是提供的初始值(initialValue))
    2、currentValue(cur): 数组中当前被处理的元素;
    3、index : 当前元素在数组中的索引;
    4、array : 调用 reduce 的数组 ;

  • Array.Includes() 方法(找到返true,否则返false)

reduce() - 如何在具有映射键值对的 JS 中将数组转换为对象?,如图:
let add = [
  {key: 'a', name: 'John'},
  {key: 'b', name: 'Doe'},
];

// js 对象与数组相互转化,对象赋值
let arr = add.reduce((els, cur) => {
	els[cur.key] = cur.name
	return els
}, {});

// 扁平一个二维数组
var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];
var res = arr.reduce((x, y) => x.concat(y), []);
let arr = [
 {'id': 1, 'name': 2}, {'id': 1, 'name': 1}, {'id': 2, 'name': 22}, {'id': 1, 'name': 'f2d1'}, {'id': 2, 'name': 'f21'}, ];
 
function deteleObject(obj) {
  let uniques = [];
  let stringify = {};
  
  // 排序
  obj.sort(function (a, b, id='id') {
    return Number(a[id]) - Number(b[id]);
  });
  
  for (let i = 0; i < obj.length; i++) {
  	if(typeof obj[i] == "object") {
      let keys = Object.keys(obj[i]);
      let str = "";
      for (let j = 0; j < keys.length; j++) {  // 遍历对象的键名(keys[j])和值(obj[i][keys[j]])
        str += JSON.stringify(keys[j]);
        str += JSON.stringify(obj[i][keys[j]]);
        // console.log(str); 			      // 此时输出 "id"1"name"1
      }
      if (!stringify.hasOwnProperty(str)) {   // 忽略重复对象
        uniques.push(obj[i]);
        stringify[str] = true;
      }
  	} else {
      if (!stringify.hasOwnProperty(obj[i])) { // 纯数组模式
        uniques.push(obj[i]);
        stringify[obj[i]] = true;
      }
  	}
  }
  return uniques
}
console.log(deteleObject(arr));

// 去重
let arr = [1,2,3,4,4,1]
let newArr = arr.reduce((pre, cur) => {
  if(!pre.includes(cur)) {
    return pre.concat(cur)
    
  } else {
    return pre
  }
}, []);  // [1, 2, 3, 4]

const FTisES6ReduceObjectNum = (arr, newobj = {}) => arr.reduce((preVal, curVal) => {
  // newobj[curVal.id] 查看是否有 id 属性值相同的一项,没有则将当前项 curVal 加入数组 preVal ,
  // 然后返回修改后数组,作为下一次的初始值进行遍历
  newobj[curVal.id] ? '' : newobj[curVal.id] = true && preVal.push(curVal); 
  return preVal
  
}, []);
  • Array.concat() - 用于连接两个或多个数组;
  • Array.isArray() - 用于判断一个值是否为数组;
// 求乘积
let arr = [1, 2, 3, 4];
let sum = arr.reduce((x, y) => x + y, 0);

// 将二维数组转化为一维
let arr = [[0, 1], [2, 3], [4, 5]]
let newArr = arr.reduce((pre,cur)=>{
    return pre.concat(cur)
},[])
console.log(newArr); // [0, 1, 2, 3, 4, 5]

// 将多维数组转化为一维
let arr = [[0, 1], [2, 3], [4,[5,6,7]]]
const newArr = function(arr) {
   return arr.reduce((pre, cur) => pre.concat( Array.isArray(cur) ? newArr(cur) : cur ), [])
}
console.log(newArr(arr)); //[0, 1, 2, 3, 4, 5, 6, 7]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值