前端ES系列

【 ES6~ES13】

1、ES6常用的特性

一.var、let、const之间的区别以及和闭包的关联

  • var声明变量可以重复声明,而let不可以重复声明,var 和 let 声明的是变量,可更改值,const 不可。
  • var 和 let 声明的是变量,可更改值,const 不可,
  • var 声明的变量只具有函数作用域, let 和 const 声明的变量和常量具有块级作用域
  • var可以在声明的上面访问变量,而let有暂存死区,在声明的上面访问变量会报错
  • let 不存在变量提升,var存在
  • const 定义一个常量,声明后不能修改,其它特点同let

var变量提升 (使用var声明的变量将会被提升到函数的顶部)

console.log(a);// undefined
var a = 2;
console.log(a) // 2

以上代码相当于

var a
console.log(a) // undefined
a=2
console.log(a) // 2

暂存死区
随着let和const的引入,也引入了暂存死区的概念。使用var的时候,作用域内(函数作用域),在还没使用var声明一个变量的时候,访问该变量,将会获得undefined。但是如果使用let,作用域(块级作用域)内,在还没使用let声明一个变量的时候,访问该变量,将会获得ReferenceError,从作用域开始到let语句之间,就是暂存死区

{
 console.log(a) // Uncaught ReferenceError: a is not defined 
 console.log(b) // Uncaught ReferenceError: b is not defined  
 console.log(c) // undefined
 // 以上区域则为暂存死区
 let a =1
 const b=2
 var c=3
}

作用域
变量的作用域无非就是两种:全局变量和局部变量。
全局作用域:
最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的:

    var outerVar = "outer";
      function fn(){
         console.log(outerVar);
      }
      fn();//result:outer

局部作用域:
和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的,最常见的例如函数内部

     function fn(){
         var innerVar = "inner";
      }
      fn();
      console.log(innerVar);// ReferenceError: innerVar is not defined

需要注意的是,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
作用域链
我的理解就是,如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
每个函数在执行中都会产生一个执行环境,js的执行顺序是根据函数的调用来决定的,当一个函数被调用时,该函数环境的变量对象就被压入一个环境栈中。而在函数执行之后,栈将该函数的变量对象弹出,把控制权交给之前的执行环境变量对象。

     var scope = "global"; 
      function fn1(){
         return scope; 
      }
      function fn2(){
         return scope;
      }
      fn1();
      fn2();

在这里插入图片描述
像上面这种内部函数的作用域链仍然保持着对父函数活动对象的引用,就是闭包
闭包
我的理解:1子函数可以使用父函数的局部变量 2闭包就是可以读取到其他函数的局部变量的函数Js语言中只有子函数才可以读取局部变量,所以闭包的定义可以理解为 闭包就是函数内部和外部链接的一个桥梁
作用:

  • 第一个就是可以读取自身函数外部的变量(沿着作用域链寻找)
  • 第二个就是让这些外部变量始终保存在内存中
    关于第二点看次代码
    function outer(){
         var result = new Array();
         for(var i = 0; i < 2; i++){//注:i是outer()的局部变量
            result[i] = function(){
               return i;
            }
         }
         return result;//返回一个函数对象数组
         //这个时候会初始化result.length个关于内部函数的作用域链
      }
      var fn = outer();
      console.log(fn[0]())//result:2
      console.log(fn[1]())//result:2
      
      for(let i = 0; i < 3; i++) {
          setTimeout(function() {
           console.log(i);
          }, 1000);
      }
    如果var改为let声明则打印123 (let使变量只在作用域内有效)

调用fn0的作用域链图:
在这里插入图片描述
可以看到result[0]函数的活动对象里并没有定义i这个变量,于是沿着作用域链去找i变量,结果在父函数outer的活动对象里找到变量i(值为2),而这个变量i是父函数执行结束后将最终值保存在内存里的结果。
闭包的作用: 正常函数执行完毕后,里面声明的变量被垃圾回收处理掉,但是闭包可以让作用域里的 变量,在函数执行完之后依旧保持没有被垃圾回收处理掉。
js函数内的变量值在编译的时候就确定的,而是等在运行时期再去寻找的。最后总结一下闭包的好处与坏处
好处
①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
③匿名自执行函数可以减少内存消耗坏处
①其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响
闭包的使用场景
最常用的就是防抖(在事件被触发n秒后再执行回调)、封装私有变量比如计数器、迭代器等

function VueDebounce(fn, time) {
  let timer = null; // 可以在使用完变量后手动为它赋值为null;减少内存消耗
  return function() {
    if (timer) {
       //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
       clearTimeout(timer );  
       timer = setTimeOut(fn,time)  // ps:原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。
    }
  };
}

1.变量的解构赋值
从数组和对象中提取值,对变量进行赋值,这被称为解构

  • 数组的解构赋值 let [a, b, c] = [1, 2, 3];
  • 对象的解构赋值let { foo, bar } = { foo: ‘aaa’, bar: ‘bbb’ };
  • 字符串的解构赋值
  • 数值和布尔值的解构赋值
  • 函数参数的解构赋值

2.模板字符串: ``
3.正则的扩展: RegExp

二.箭头函数和普通函数的区别以及this的指向

1、 this指向问题

  • 箭头函数的this指向的是父级作用域的this,是通过查找作用域链来确定 this的值也就是说,看的是上下文的this,指向的是定义它的对象,而不是使用时所在的对象;普通函数指向的是它的直接调用者。
  • 箭头函数的this

let obj = {
        a: 1,
        b: () => {
            console.log(this.a); // undefined
        },
        c: function() {
            console.log(this.a); // 1 
        },
    };
obj.b();
obj.c();

箭头函数没有this,它的this是继承来的,默认指向定义它的时候的对象,就是我们说的宿主对象,而不是执行它的对象。这里通过obj.b(),此时this指向的window对象,上面没有a,所以返回undefined。通过obj.c(),this指向的是它的直接调用者,就是obj,所以返回。
2.不可以被当作构造函数
不能被当作构造函数来使用,通过new命令来作为构造函数会报错,这里没有构建原型的说法,不存在prototype这个属性,也不能通过super访问原型的属性,而且new target也是不能用的。
3、不可以使用arguments对象,该对象在函数体内不存在,如果要用就用rest参数替代。
4、不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
总结
1、凡是在函数内部调用的函数 this 都指向window
2、在事件中一般情况下 this 的指向都指向当前对象
3、在对象的函 数中一般情况 this 的指向都指向当前对象
4、计时器 this 的指向都指向window
5、如果有new关键字,this指向new出来的那个对象
怎么改变 this 的指向

  • 在函数内部使用 _this = this,或者let that = this,使用that作为中间变量来解决
  • apply(thisArg, [argsArray])、call(thisArg, arg1,arg2, …)、bind
    call方法:
    语法:call(thisObj,Object)
    定义:调用一个对象的一个方法,以另一个对象替换当前对象
    说明:call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
    apply方法:
    语法:apply(thisObj,[argArray])
    定义:应用某一对象的一个方法,用另一个对象替换当前对象。
    说明:如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。
    bind方法:bind 和 call,apply 也有相似之处,bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
let vm = new Vue({
  data(){
    return {
      height:768
    }
  },
  mounted(){
    window.addEventListener("resize",(function(){
      this.height = document.body.clientHeight
    }).bind(this)) // 如果不绑定this,则回调函数内this为 window ,显然读取不到this.height
  }
})

4.数组的扩展 扩展运算符、includes()等
5.对象的扩展

  • 常用遍历对象的属性
    (1)for…in
    for…in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
    (2)Object.keys(obj)
    Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
  • super 关键字:
    this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象
  • 扩展运算符{…obj}
    6.对象的新增方法 Object.assign() 和Object.keys()等
    7.Symbol 表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
    7.Set 和 Map 数据结构 ES6中新的数据结构Set:它类似于数组,但是成员的值都是唯一的,没有重复的值。Set本身是一个构造函数,用来生成 Set 数据结构 。常使用次方法进行数组去重 […new Set(arr)] ,let list=new Set(); (增add) list.add(1),(删delete) list.delete(1) 等方法
    ES6中Map数据结构:类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

三.Promise

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise对象有以下两个特点。
1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise也有一些缺点。
首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

1.简述Promise原理

// promise里面只有一个参数executor,默认new时就会调用,且 默认promise的executor是同步执行
function Promise(executor){ //executor执行器
    let self = this;
    self.status = 'pending'; // 等待状态
    self.value  = undefined; // 表示当前成功的值
    self.reason = undefined; // 表示是失败的值
    function resolve(value){ // 成功的方法
        if(self.status === 'pending'){
            self.status = 'resolved';
            self.value = value;
        }
    }
    function reject(reason){ //失败的方法
        if(self.status === 'pending'){
            self.status = 'rejected';
            self.reason = reason;
        }
    }
    executor(resolve,reject);
}
// 每一个Promise实例上都有一个then方法,then方法中有两个参数,一个参数叫成功的函数onFufiled一个叫失败函数onRejected
Promise.prototype.then = function(onFufiled,onRejected){
    let self = this;
    if(self.status === 'resolved'){
        onFufiled(self.value); // self.vue 成功的原因
    }
    if(self.status === 'rejected'){
        onRejected(self.reason); // self.reason失败的原因
    }
}
module.exports = Promise;
  • 构造一个Promise实例需要给Promise构造函数传入一个函数。传入的函数需要有两个形参,两个形参都是function类型的参数。分别是resolve和reject。
  • Promise上还有then方法(ps:then方法是异步调用的事件环),then 方法就是用来指定Promise 对象的状态改变时确定执行的操作,resolve 时执行第一个函数(onFulfilled),reject时执行第二个函数(onRejected)
  • 当状态变为resolve时便不能再变为reject,反之同理。

ps:(无论成功(resolve)还是失败(rejected)都会走then只不过成功走then中成功的回调,错误会走失败的回调,此时如果统一处理使用catch(和then的第二个参数一样)可以直接把error函数提出来走catch)

const promise = new Promise((resolve, reject) => {
   setTimeout(() => {
       resolve('success')
   })
})
.then(value => { ... }, reason => { ... })
.catch(error => { ... }) // error是从then里面提出来的
// 相当于调用 then 方法, 但只传入 Rejected 状态的回调函数
// 添加catch方法
catch (onRejected) {
  return this.then(undefined, onRejected)
}

ES6 将某一件可能发生异步操作的事情,分为两个阶段:unsettled 和 settled

  • unsettled:未决阶段,表示事情还在进行前期的处理,并没有发生通向结果的那件事
  • settled:已决阶段,事情已经有了一个结果,不管这个结果是好是坏,整件事情无法逆转

异步操作总是从 未决阶段 逐步发展到 已决阶段的。并且,未决阶段拥有控制何时通向已决阶段的能力
异步操作分成了三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)

  • pending:进行中,处于未决阶段,则表示这件事情还在进行(最终的结果还没出来)
  • fulfilled:已成功,已决阶段的一种状态,表示整件事情已经出现结果,并是一个可以按照正常逻辑进行下去的结果
  • rejected:已失败,已决阶段的一种状态,表示整件事情已经出现结果,并是一个无法按照正常逻辑进行下去的结果,通常用于表示有一个错误
  • 一旦这种状态改变,就会固定,不会再改变。是不可逆的Promise状态的改变只有两种情况:

参考流程图:
Promise状态转换
在这里插入图片描述

2.Promise的方法then,catch,finally

then()和catch()
then():注册一个后续处理函数,当Promise为resolved状态时运行该函数
catch():注册一个后续处理函数,当Promise为rejected状态时运行该函数
Promise对象中,无论是then方法还是catch方法,它们都具有返回值,返回的是一个全新的Promise对象,它的状态满足下面的规则:

  • 如果当前的Promise是未决的,得到的新的Promise是进行中状态
  • 如果当前的Promise是已决的,会运行响应的后续处理函数,并将后续处理函数的结果(返回值)作为resolved状态数据,应用到新的Promise中;如果后续处理函数发生错误,则把返回值作为rejected状态数据,应用到新的Promise中。

finally()
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作

注意:后续的Promise一定会等到前面的Promise有了后续处理结果后,才会变成已决状态

3.async 和 await

async 和 await 是 ES2016 新增两个关键字目的是简化 Promise api 的使用,并非是替代 Promise。际上就是生成器函数的一个语法糖。目的是简化在函数的返回值中对Promise的创建。
(1)async
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
async 用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。

async function test(){
    console.log(1);
    return 2;//完成时状态数据
}
//等同于
function test(){
    return new Promise((resolve, reject)=>{
        console.log(1);
        resolve(2);
    })
}

注意:async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
(2)await
正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。await类似于生成器的yield,当遇到await的时候,就会等待await后面的Promise对象执行完毕后再继续执行下面代码。

async function test(){
    const namePro = await getName();//异步ajax
    const passwordPro = await getPassword();
}

test()函数执行过程中,会先等待getName()执行完毕后,再执行getPassword()
注意:await关键字必须出现在async函数中
如果多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

async function test(){
    let namePro = getName();
    let passwordPro = getPassword();
    let name = await namePro;
    let password = await passwordPro;
}

先让getName()和getPassword()执行,然后再等待结果
await用在某个表达式之前,如果表达式是一个Promise,则得到的是thenable中的状态数据。

async function test1(){
    console.log(1);
    return 2;
}

async function test2(){
    const result = await test1();
    console.log(result);
}

test2();
//等同于
function test1(){
    return new Promise((resolve, reject)=>{
        console.log(1);
        resolve(2);
    })
}
function test2(){
    return new Promise((resolve, reject)=>{
        test1().then(data => {
            const result = data;
            console.log(result);
            resolve();
        })
    })
}
test2();

如果await的表达式不是Promise,则会将其使用Promise.resolve包装后按照规则运行

async function test(){
    const result = await 1
    console.log(result)
}
//---等同
function test(){
    return new Promise((resolve,reject)=>{
        Promise.resolve(1).then(data =>{
            const result = data
            console.log(result)
            resolve()
        })
    })
}

四.Proxy 和defineProperty 以及两者区别

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。Proxy用自己的话理解的话就是一个拦截器或者是过滤器。感觉本质上就是在被代理的目标对象上加了一些事件行为
语法:const p = new Proxy(target, handler)
参数

  • target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
    handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
  • handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。
  • 所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。
let obj = {a:5}
const proxyObj = new Proxy(obj,{
get:funtion(){
   return 'proxy';
  },
  set:funtion(){
  }
})
console.log(obj.a); // 5
console.log(proxyObj.a); proxy

Proxy的handler对象上的方法get方法用于拦截某个属性的读取操作,可以接受三个参数,get(target, propKey, receiver):
get(target(目标对象), propKey(被获取的属性名), receiver(Proxy或者继承Proxy的对象 返回值)):

var person={ name: "张三"}
 var proxy = new Proxy(person,(
  get:funtion(target,propKey){
  if (propKey in target) { 
      // 有这个属性就返回
      return target[propKey];
  }else{
    // 没有这个属性就报错
     throw new ReferenceError('error)
  }
))
proxy.name // 张三
proxy.age // error   没有这个属性就报错

下面是一个get方法的第三个参数(receiver)的例子,它总是指向原始的读操作所在的那个对象,一般情况下就是 Proxy 实例。

const proxy = new Proxy({}, {
  get: function(target, key, receiver) {
    return receiver;
  }
});
proxy.getReceiver === proxy // true

上面代码中,proxy对象的getReceiver属性是由proxy对象提供的,所以receiver指向proxy对象

const proxy = new Proxy({}, {
  get: function(target, key, receiver) {
    return receiver;
  }
});

const d = Object.create(proxy);
d.a === d // true

上面代码中,d对象本身没有a属性,所以读取d.a的时候,会去d的原型proxy对象找。这时,receiver就指向d,代表原始的读操作所在的那个对象。
set() set(target, property, value, receiver)
set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

let validator = {
    set(target,prop,val){
        if(prop === 'age'){
            if(!Number.isInteger(val)){
                throw new TypeError('The age is not ange integer')
            }
            if(val>200){
                throw new RangeError('The age is invalid')
            }
        }
        target[prop] = val
    }
}
let person = new Proxy({},validator)
person.age = 100
console.log(person.age); // 100当大于200报错

this 问题 虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m()  // true

上面代码中,一旦proxy代理target,target.m()内部的this就是指向proxy,而不是target。所以,虽然proxy没有做任何拦截,target.m()和proxy.m()返回不一样的结果。
Web 服务的客户端 实例
Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端。

const service = createWebService('http://example.com/data');
service.employees().then(json => {
  const employees = JSON.parse(json);
  // ···
});

上面代码新建了一个 Web 服务的接口,这个接口返回各种数据。Proxy 可以拦截这个对象的任意属性,所以不用为每一种数据写一个适配方法,只要写一个 Proxy 拦截就可以了。

function createWebService(baseUrl) {
  return new Proxy({}, {
    get(target, propKey, receiver) {
      return () => httpGet(baseUrl + '/' + propKey);
    }
  });
}

用ES5的Object.defineProperty也可以实现拦截
用ES5的Object.defineProperty实现get和set的拦截也是可以的,但是由于此方法开始时是针对某个属性来设置的,所以需要一些操作,有点麻烦

const obj = {
    one: 1,
    two: 2,
    three: 3
}
for (var key in obj) {
    let val = obj[key]
    Object.defineProperty(obj, key, {
        get() {
            return val
        }
    })
}
console.log(obj.one); //1

使用Object.defineProperty实现简单的双向数据绑定原理

<body>
  手写一个简单双向绑定<br/>
  <input type="text" id="model"><br/>
  <div id="modelText"></div>
</body>
<script>
var user = {};
var defaultName = "一开始显示的值";
 
document.querySelector("#model").value = defaultName;
document.querySelector("#modelText").textContent = defaultName;
 
//定义属性 监控改变
Object.defineProperty(user,"name",{
  get:function(){
    console.log("来获取的值");
    return defaultName;
  },
  set:function(newValue){
    console.log("设置新值");
    defaultName = newValue;
    console.log("实现 模型 => 视图");
    document.querySelector("#model").value = newValue;
    document.querySelector("#modelText").textContent = newValue;
  }
})
 
console.log("2s 后改变值");
 
setTimeout(() => {
  //改变值
  user.name = "2s后改变的值";
}, 2000);
</script>

Object.defineProperty方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象

  • value: 设置属性的值
  • writable: 值是否可以重写。true | false
  • enumerable: 目标属性是否可以被枚举。true | false
  • configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false
  • set: 目标属性设置值的方法
  • get:目标属性获取值的方法

使用proxy和reflect实现简单的双向数据绑定原理

<body>
    <h1>使用Proxy 和 Reflect 实现双向数据绑定</h1>
    <input type="text" id="input">
    <h2>您输入的内容是: <i id="txt"></i></h2>
    <script>
        //获取dom元素
        let oInput = document.getElementById("input");
        let oTxt = document.getElementById("txt");
        //初始化代理对象
        let obj = {};
        //给obj增加代理对象
        let newProxy = new Proxy(obj,{
            get: (target,key,recevier)=>{
               //  Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefined。
                return Reflect.get(target,key,recevier);
            },
            set: (target,key,value,recevier)=>{
                //监听newProxy是否有新的变化
                if(key == "text"){
                    oTxt.innerHTML = value;
                }
                // Reflect.set方法设置target对象的name属性等于value。
                //将变化反射回原有对象
                return Reflect.set(target,key,value,recevier);
            }
        })
        //监听input输入事件
        oInput.addEventListener("keyup",(e)=>{
            //修改代理对象的值
            newProxy.text = e.target.value;
            console.log(newProxy);
        })
    </script>
</body>

Reflect
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。
(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
(2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
所以常常Proxy和Reflect搭配使用

总结:
Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)
其实就是在对目标对象的操作之前提供了拦截,可以对外界的操作进行过滤和改写,修改某些操作的默认行为,这样我们可以不直接操作对象本身,而是通过操作对象的代理对象来间接来操作对象,达到预期的目的~
defineProperty和Proxy对比:
Object.defineProperty只能劫持对象的属性,而Proxy是直接代理对象。

  • Object.defineProperty只能劫持对象的属性,而Proxy是直接代理对象。
    由于 Object.defineProperty 只能对属性进行劫持,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历。而 Proxy 直接代理对象,不需要遍历操作。
  • Object.defineProperty对新增属性需要手动进行Observe。
    由于 Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象(改变属性不会自动触发setter),对其新增属性再使用 Object.defineProperty 进行劫持。
    也正是因为这个原因,使用vue给 data 中的数组或对象新增属性时,需要使用 vm.$set 才能保证新增的属性也是响应式的。
  • defineProperty会污染原对象(关键区别)
    proxy去代理了ob,他会返回一个新的代理对象不会对原对象ob进行改动,而defineproperty是去修改元对象,修改元对象的属性,而proxy只是对元对象进行代理并给出一个新的代理对象。

五.class类

在传统的js里面是只有对象,没有类的概念的。JavaScript 语言中,生成实例对象的传统方法是通过构造函数

function Person(name,age) {
    this.name = name;
    this.age=age;
}
Person.prototype.say = function(){
    return "我的名字叫" + this.name+"今年"+this.age+"岁了";
}
var obj=new Person("张三",25);//通过构造函数创建对象,必须使用new 运算符
console.log(obj.say());//我的名字叫张三今年25岁了

构造函数生成实例的过程:

  • 当使用构造函数,并且new 构造函数(),后台会隐式执行new Object创建对象;
  • 将构造函数的作用域给新对象,(既new Object()创建出的对象),而函数体内的this就代表new Object出来的对象
  • 执行构造函数代码
  • 返回新对象

ES6中引进了Class类的概念 。ES6中的类其实就是构造函数的另外一种写法,类自身就是指向构造函数。

  • 在类中声明方法时,不能给该方法加上function关键字
  • 方法之间不要用逗号分隔,会报错
    将上文代码改为ES6的写法如下:
class Person{ // 定义一个名字的person的类
  constructor(name,age) {
  this.name = name;
  this.age = age;
  }
  say(){ //这是一个类的方法,注意千万不要加上function
    return "我的名字叫" + this.name+"今年"+this.age+"岁了";
  }
}
var obj=new Person("张三",25);
console.log(obj.say());//我的名字叫张三今年25岁了

所有的类的方法都定义在类的prototype属性上面

Person.prototype.say=function(){//定义与类中相同名字的方法。成功实现了覆盖!
    return "我是来证明的,你叫" + this.name+"今年"+this.age+"岁了";
}
var obj=new Person("张三",25);
console.log(obj.say());//我是来证明的,你叫张三今年25岁了

construct方法时类的构造函数的默认方法,通过new命令生成对象实例,自动调用。

class Box{
    constructor(){
        console.log("学习很开心");//当实例化对象时该行代码会执行。
    }
}
var obj=new Box();

class不存在变量提升,所以需要先定义再使用。因为ES6不会把类的声明提升到代码头部,但是ES5就不一样,ES5存在变量提升,可以先使用,然后再定义。

六.class类的继承

ES6中引入了class关键字,class可以通过extends关键字实现继承,还可以通过static关键字定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
Es5的继承,实质是先创造子类的实例对象的this,然后在将父类的方法添加到this上面Parent.apply(this)。ES6的继承机制则完全不同,实质先将父类实例对象的属性和方法,加到this上面,所以先调supper方法,然后在调用构造函数修改this
需要注意的是,class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的。

class father{
 constructor(phone,age){
   this.phone = phone;
   this.age= age;
 }
 showName(){
 	console.log("这是父方法");
	console.log(this.phone,this.age);
 }
}

let  fatherData= new father("134***0122","40");
console.log(fatherData);

class son extends father{
 constructor(name,age,salary){
     super(name,age);
     this.salary = salary;
  }
  showName(){
     console.log("这是子的方法");
	 console.log(this.name,this.age,this.salary);
  }
}
let baby = new son3("baby","2","helloWorld");
console.log([baby]);

class继承语法简单易懂,操作更方便,但是并不是所有的浏览器都支持class关键字。
未完待续~

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT温故而知新

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值