Skip to content

Latest commit

 

History

History
635 lines (547 loc) · 20 KB

es6.md

File metadata and controls

635 lines (547 loc) · 20 KB

es6

  • 近10000字的ES6语法知识点补充
  • (已整理)图解 Map、Reduce 和 Filter 数组方法
    
    
  • (已整理)var let const的区别及使用场景
    首先3个都是js声明变量所用
    Var
      var 所声明的变量,作用域为该语句所在的函数内,且存在变量提升
      
      console.log(a) // 变量提升,var a 被提至所在作用域顶部, 所以这里输出的值为 undefined
      var a = 'JS每日一题'
      for (var i = 0; i <10; i++) {  
        setTimeout(function() {  // 同步注册回调函数到 异步的 宏任务队列。
          console.log(i);        // 执行此代码时,同步代码for循环已经执行完成
        }, 0);
      }
      
      10 ... 10 // 10个10
      
      console.log(i) // i 作用域全局 输出10
      后面声明的会覆盖之前声明的变量
      
      var a = 'JS'
      var a = 'JS每日一题' // JS每日一题
    
    Let
      let 所声明的变量,作用域为该语句的代码块内,不存在变量提升
      
      console.log(a) // 变量没有被提升, 输出 ReferenceError: a is not defined
      let a = 'JS每日一题'
      
      
      for (let i = 0; i <10; i++) {  
        setTimeout(function() {  // 同步注册回调函数到 异步的 宏任务队列。
          console.log(i);        // 执行此代码时,同步代码for循环已经执行完成
        }, 0);
      }
      
      1...10 // 1到10
      
      console.log(i) // i作用域for 块级内,输出 i is not defined
      不允许重复声明
      
      let a = 'JS每日一题'
      let a = 'JS' // 'a' has already been declared
    
    Const
      const 包含let 所有特性, 区别是const声明的变量是一个只读的不可修改的
      这里注意,const保证的不是所声明的值不得改动, 而是变量指向的内存不可改动
      代码示例
      
      const a = {
        content: 'JS每日一题'
      }
      
      a.content = 'JS'  // JS
      
      a = {
        content: 'JS' // 这里改变了内存, 所以报错 Assignment to constant variable
      }
    三句话总结
      使用var声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象,后面的覆盖前面的
      使用let声明的变量,其作用域为该语句所在的代码块内,不存在变量提升, 不能重复声明
      使用const声明的是常量,在后面出现的代码中不能再修改该常量的内存
    
  • (已整理)如何理解es6中的类?
    首先,JS作为一门非面向对象语言,在es6之前,并没有提供对类的支持,我们常用的做法是通过构造函数来模拟类的实现,
     通过将属性及方法定义在原型上共享给其实例
    
    简单实现:
      function JS(name) {
        this .name = name
      }
      
      JS.prototype.getName = function(){
      console.log(this .name)
      }
      
      const child = new JS('每日一题')
    ES6 中的Class
    es6中的class只是一个语法糖,class的写法只是让对象原型看起来更加清晰
    简单使用:
      class JS {
        constructor(name) {
          this.name = name
        }
      
        getName() {
          console.log(this .name)
        }
      }
    
      const child = new JS('每日一题')
    
    每个类中都有一个constructor方法,如果没有显示定义, 会默认添加一个空的constructor,
    等同于ES5中的构造函数, 类的所有方法都是定义在类的prototype属性上面,
    二者的主要区别在于Class必须使用new调用, ES5中构造函数不使用new也可以调用, 
    class 中新增静态方法(static)关键字, 静态方法不能被继承只有通过类本身来调用
    
    class JS {
      constructor(name) {
        this .name = name
      }
    
    
      static getName() {
        // static 方法只有类本身能调用,实例不能继承
        console.log(this.name)
      }
    }
    Extends 继承
    class 也可以通过extends 关键字实现继承
    
    代码示例:
      class JS {
        constructor(name) {
          this.name = name
        }
      
        getName() { 
          console.log(this.name)
        }
      }
    
      class Css extends Js {
        constructor() { 
          super();
        }
      }
    
      const child = new Css('JS每日一题')
      child.getName()// 'JS每日一题'
    
    extends 注意点
      使用extends 继承时,子类构造函数中必须调用super(), 代表调用父类的构造函数
      super虽然代码父类的constructor,但是返回的子类的实例
      super作为函数调用时,代表类的构造函数
      super作为对象调用时, 在普通方法中,指向父类的原型对象, 静态方法中指向父类
    
  • (已整理)如何理解es6中的Promise?
  js是单线程的,也就是说一次只能完成一个任务,为了解决这个问题,js将任务的执行模式分为两种, 同步和异步,
   在es5中我们处理异步只能通过的回调的方式进行处理,在多层异步中,回调会一层一层嵌套,也就是所谓的回调地狱,
   promise就是异步编程的一种解决方案

  Promise
    特点:
      对象的状态不受外界影响, promise对象代表一个异步操作,有三种状态pendding(进行中), 
      fulfilled(成功), rejected(失败)
      一旦状态改变,就不会再变, 状态的改变只有两种可能, pendding => fulfilled及pendding => rejected
    基本用法:
      const promise=new Promise(function(resolve,reject){
        // ... some code
        if(/* 异步操作成功 */){
          resolve(value);
          // 将状态从pendding变成fulfilled
        }else{
          reject(error);
          // 将状态从pendding变成rejected
        }
      });
    promise 生成实例后可以使用then方法接收resolved状态和rejected状态的回调函数

    promise.then(()=>{
      console.log('resolved')
    },()=>{
      console.log('rejected')
    })
    
    promise原型上具有catch方法, catch方法是rejection的别名, 用于指定发生错误时的回调函数

    promise.then(()=>{
      console.log('resolved')
      },
      ()=>{
        console.log('rejected')
      }).catch((err)=>{
        console.log('catch')
      })
    promise原型上具有finally方法,用于不管promise对象最后的状态如何,都会执行的操作

    promise.then(()=>{
      console.log('resolved')
    },()=>{
      console.log('rejected')
    }).finally((err)=>{
      console.log('end')
    })
  
  Promise.all
    Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例

  简单使用:
    特点:
    参数都是promise实例,如果不是会调用promise.resolve方法将其转为promise实例
    p的奖态由传入的promise实例的状态决定
      promise实例状态都变成fulfilled,p状态为fulfilled
      promise实例状态一个变成rejected,p状态为rejected
  • (已整理) 如何理解es6中的Proxy?
    Proxy(代理) 定义
    可以理解为为目标对象架设一层拦截,外界对该对象的访问,都必须通过这层拦截
    示例:
      const obj = new
      Proxy({}, {
          get: (target, key, receiver) = >{
              return 'JS'
              console.log(`get $ {key}`)
          },
          set: (target, key, value, receiver) = >{
              console.log(`set $ { key }`)
          },
      })
      obj.name = 'JS 每日一题'
      // set name
      // JS 每日一题
      obj.name
    
      // 这里进入get的回调函数,所有直接返回 JS
    从上面的示例中可以看出,Proxy存在一种机制,可以对外界的读写操作进行改写
    
    Proxy 实例方法
    proxy除了代理get,set操作,还能代理其它的操作,如下
      handler.getPrototypeOf()
      // 在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。
    
      handler.setPrototypeOf()
      // 在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。
    
      handler.isExtensible()
      // 在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。
    
      handler.preventExtensions()
      // 在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。
    
      handler.getOwnPropertyDescriptor()
      // 在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。
      
      handler.defineProperty()
      // 在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时。
      
      handler.has()
      // 在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。
    
      handler.get()
      // 在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
    
      handler.set()
      // 在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。
    
      handler.deleteProperty()
      // 在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。
    
      handler.ownKeys()
      // 在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。
    
      handler.apply()
      // 在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。
    
      handler.construct()
      // 在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。
    
    
  • (已整理)如何理解es6中的Decorator(装饰器?
  Decorator是ES7中的提案,概念借鉴于python, 它作用于一个目标类为其添加属性于方法
  我们用一个比喻来理解Decorator, 把孙悟空看成是一个类,那么棒子就是装饰器为其装备的武器
  代码理解:
    @stick class
    
    Monkey{
    
    }
    
    function stick(target) {
      // 第一个参数就是目标类的本身
      target.ATK = 100000
    }
    
    Monkey.ATK
    // 为悟空装备了棒子,攻击力提高了100000
    // 如果一个参数不够用,可以在装饰器外层再包一层
    function stick(atk) {
      return function(targt) {
        target.ATK = atk
      }
    }
    
    @stick(200000)
    // 这样我们就为悟空增加了200000攻击力
    class Monkey{
    
    }
    
  Decorator 不仅能修饰类,也能修饰类的方法
  class Monkey {
    @setName 
    name() {
        this.name = '孙悟空'
    }
  }
  
  Decorator 只能修饰类及类的方法,不能修饰于函数,因为存在函数提升

  Mixin
    在修饰器基础上,我们可以实现mixin(混入),意思在一个对象中混入另一个对象的方法
  
    代码示例:
      export function mixins(...list) {
          return function(target) {
              Object.assign(target.prototype, ...list)
          }
      }
      
      const skill = {
          shapeshifting() {
              console.log('72变')
          }
      }
      @mixins(skill) class Monkey {
      
      }
      
      Object.assign(Monkey.prototype, skill) 
      const swk = ne Monkey() 
      swk.shapeshifting()// 72变
  使用Decorator的好处
    扩展功能,相对于继承增加了更多的灵活性
    代码可读性更高,装饰器正确命名相当于注释
vue有哪些生命周期及其使用场景?

首先生命周期就是hook函数,简单理解就是在什么什么之前,在什么什么之后,
在vue中分为8个阶段:创建前/后、载入前/后、更新前/后、销毁前/销毁后

beforeCreate(创建前),在数据观测和初始化事件还未开始
created(创建后),完成数据观测,属性和方法的运算,初始化事件, $el 属性还没有显示出来
beforeMount(载入前),在挂载开始之前被调用,相关的render函数首次被调用。
实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。

mounted(载入后),在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。
实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。
完成模板中的html渲染到html页面中。此过程中进行ajax交互。

beforeUpdate(更新前),在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。
可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。

updated(更新后),在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,
所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。
该钩子在服务器端渲染期间不被调用。

beforeDestroy(销毁前),在实例销毁之前调用。实例仍然完全可用。

destroyed(销毁后),在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。
该钩子在服务器端渲染期间不被调用。

第一次页面加载会触发beforeCreate、created、beforeMount、mounted, mounted说明dom渲染完毕

Vue组件间如何通信?

首先搞清楚组件中的几种通信模式
  父向子级传递
  子向父级传递
  兄弟级传递
  传递的方式

  props传递
    代码示例:
    
      // parent
      < template > <child: content = "content" > </template>
      // child props:
      {
          content: String
      }
  
  $attrs, $listeners
  $attrs 和 $listeners 属性像两个收纳箱,一个负责收纳属性,一个负责收纳事件(不包含props声明过,以及内置特性绑定属性,如class,style等...)
  代码示例:
    // parent
    < template > <child: content = "content": bar = "bar"@one = "one"@two = "two"class = "child"style = "width:1rem" > </template>
    // child props: {
        content: String,
        one: Function
    }
    console.log(this.$attrs,this.$listeners)// bar, two

  $emit, $on
    代码理解:
      vm.$on('JS',function say(val) {
          // 执行事件并接收回传的参数
          console.log('JS, ' + 每日一题)
      })
      vm.$emit('JS', '每日一题')// 触发当前实例上的事件

  provide,inject
    主要为高阶组件及组件库所用,理解为允许一个祖先组件为其所有子孙组件注入依赖
    代码理解:
    // parent
    < template> <child/></template>
    provide:{
        name:'JS',
        value:'每日一题',
    },
    // child 
    inject:{
        name: String,
        value:String
    }
    console.log(this.name,this.value)// JS,每日一题

  EventBus
    思路就是声明一个全局Vue实例变量 EventBus , 把所有的通信数据,事件监听都存储到这个变量上
    代码理解:
      // Global
      let EventBus=new Vue()
      // parent
      EventBus.$on('received',function(val){
          console.log('received: ' + val)
          // received: JS每日一题
      })
      // child
      EventBus.$emit('received','JS每日一题')
  Vuex
    Vuex解决大型项目复杂通信问题
Vue双向绑定实现原理?

vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty() 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。

vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。

Observer 对所有数据的属性进行监听
Compile 对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数
Watcher 作为连接Observer 跟 Compile 之间的桥梁, 能够订阅接收到每个属性变动的通知,执行相应的回调函数
代码理解:
const Dep = function() {
  this.subs = [];
};
Dep.prototype = {
  addSub: function(sub) {
    this.subs.push(sub);
  },
  notify: function() {
    this.subs.forEach(sub = >{
      sub.update();
    });
  },
};
const Watcher = function(vm, node, name) {
  Dep.target = this;
  this.name = name;
  this.node = node;
  this.vm = vm;
  this.update();
  Dep.target = null;
};
Watcher.prototype = {
  update: function() {
    this.get();
    this.node.nodeValue = this.value;
  },
  get: function() {
    this.value = this.vm[this.name];
  },
};
const compile = function(node, vm) {
  if (node.nodeType === 1) {
    let attr = node.attributes;
    for (let i = 0; i < attr.length; i++) {
      if (attr[i].nodeName === 'v-model') {
        let name = attr[i].nodeValue;
        node.addEventListener('input', e = >{
          vm[name] = node.value;
        });
        node.value = vm[name];
        node.removeAttribute('v-model');
      }
    }
  }
  // Text 节点类型
  if (node.nodeType === 3) {
    if (///{//{(.*)//}//}/.test(node.nodeValue)) {
      let name = RegExp.$1;
      name = name.trim();
      node.nodeValue = vm[name];
      new Watcher(vm, node, name);
    }
  }
};
const observe = data = >{
  if (!data || typeof data !== 'object') return 3344;
  Object.keys(data).forEach(key = >defineReactive(data, key, data[key]));
};
const defineReactive = (data, key, value) = >{
  const dep = new Dep();
  observe(value);
  Object.defineProperty(data, key, {
    get: () = >{
      if (Dep.target) dep.addSub(Dep.target);
      return value;
    },
    set: function(newValue) {
      console.log(`数据已发生变化,新的值为$ {
        newValue
      }`);
      value = newValue;
      dep.notify();
    },
  });
};
function nodeToFragment(node, vm) {
  let flag = document.createDocumentFragment();
  let child;
  while ((child = node.firstChild)) {
    compile(child, vm);
    flag.appendChild(child);
  }
  return flag;
}
function Vue(options) {
  let data = this.data = options.data;
  observe(data, this);
  let id = options.el;
  let dom = nodeToFragment(document.getElementById(id), data);
  document.getElementById(id).appendChild(dom);
}
let vm = new Vue({
  el: 'app',
  data: {
    text: 'example text',
  },
});