告别石器时代:ES6 新特性

51 阅读15分钟

解构赋值

对于不是对象的变量会调用ToObject() 封装,null和undefine不能解构赋值

数组解构

是将数组的单元值 快速 批量 的赋值给一系列变量的简介语法

为了标记每个元素的意义

const arr = [100,10,1]
const max = arr[0]
const min = arr[2]
const avg = arr[1]
console.log(max)
console.log(min)
console.log(avg)
  • 解构写法
const arr = [100,10,1]
const [max,min,avg]  = arr
console.log(max)
console.log(min)
console.log(avg)
//这个写法和上面的传统写法是一个效果
  • 交换变量
let a = 1
let b = 2
;[b,a] = [a,b]//注意这里有个分号

注意这里有个分号 立即执行函数也要加分号

单元值和变量不对应

const [a,b,c,d] = [1,2,3]
console.log(d)//undefined
const [a,b] = [1,2,3]
//3就会漏掉

用剩余变量来接

const[a,b,...c]  = [1,2,3,4,5]
//c是一个真数组

防止undefine传递

//防止undefine传递
const [a = 0, b = 0 ] = [ ]

按需操作

//按需操作
const [a,b, , d] = [1,2,3,4]

多维数组

无脑可以嵌套解构

const arr = [1,2,[3,4]]
const [a,b,c] = arr
const [a,b,[c,d]]] = arr

对象解构

将属性和方法快速批量的赋值給变量的方法’

语法

属性名和变量名必须是一样的 所以要注意会不会和外面的变量冲突,可以更名

const obj = {
uname = 'pink',
age : 18
}
const {uname,age} = obj
//===const uname = obj.uname//pink

解构变量改名

oldname : newname

const {uname:username ,age} = obj

对象数组解构

const pig = [
{uname :'佩奇',
 age:18
}]
const [{uname,age}] = pig
console.log(uname)
console.log(age) 

多级对象解构

注意加上内对象的名字加:

const pig {
name :'pink',
family :{
mother:1,
father:2,
sister:3
},
age: 6
}
const {name,family: {mother,father,sister} } = pig

给已经声明的变量解构赋值

let personName,personAge
let person = {
name : 'matt',
age: 7
}

//要加一个括号全部括起来
({name:personName,age: personAge} = person)

函数进阶

函数声明

  • 声明式

    • function FunName(){}
  • 表达式

    • let FunName = function () {}
    • 其实就是声明一个匿名函数然后赋值给FunName
  • 箭头函数式

    • let FunName = () = > {}
  • 不建议用函数对象的实例化来定义函数,效率不高可读性差

函数声明提升

类似于变量提升 会把函数的声明提升到当前 作用域 的最顶部

fn() //会把函数的声明提升到当前作用域的最顶部
functino fn() {
console.log(
提升
)
}

可以

不提升调用 不提升赋值

fun()
var fun = funtion (){console.log()}

相当于

var fun()
fun()
fun = funtion () {}

不可以 函数表达式必须声明赋值好再调用

函数参数

传递参数

  • 红宝书解释

    • ECMAScript中的所有函数的传参都是按值传递

      • 什么意思呢,就是说函数外的变量会直接复制到函数内部

      • 如果参数是一个原始值,那么就相当于直接变成一个新的局部变量

        • 所以函数内部修改不会反映到函数外部
      • 如果传递的是一个对象这种引用类型的数据,**传递的也是值而不是引用,**虽然内部的修改复制的值来访问对象,可以将内部的修改映射到外部,因为obj指向的对象保存再堆内存上

        • 这并不意味着是按照引用传递的;证明如下
      •   function setName(obj){
                  obj.name = 'Nicholas';
                  obj = new Object()
                  obj.name = "Greg"
          }
          let person = new Object()
          setName (person)
          console.log(person.name) // 'Nicholas'
        
      • 📌 这个观点可能不够全面但是却证明了引用值不是按引用传递的
  • 我更喜欢的解释

    •   function changeStuff(a, b, c) {
          a= a* 10;
          b.item= "changed";
          c= {item: "changed"};
        }
        var num= 10;
        var obj1= {item: "unchanged"};
        var obj2= {item: "unchanged"};
        changeStuff(num, obj1, obj2);
        console.log(num);
        console.log(obj1.item);
        console.log(obj2.item);
      
    •   10
        changed
        unchanged
      
  • 传递的对象的指针的拷贝,如何理解呢

    • 对于原始值,拷贝了指针,次指针原本指向的是实参,重新赋值之后相当于给这个指针拷贝改变了指向,所以原来的指针对应的实参的值当然不会发生改变
    • 对于引用值,拷贝了指针,而不是指针本身,用这个拷贝来的指针访问到的值实际上就是本来的实参的真实值,但是直接对这个指针赋值,只会改变这个指针的拷贝的指向
  • 初始状态

  • 函数域出现,拷贝出指针ab

  • 执行操作a.item = changed,b = {item : changed}

  • 所以得到的结果obj1改变了,obj2没有改变,原始值的状况就和obj2一样

基本类型可以称为传值调用没错,引用类型是传共享调用,本质上就是传变量的指针的拷贝,而引用类型的值就是一个指针,所以也可以称为值传递,所以说javascript的所有数据类型都是值传递的是没有问题的

默认参数作用域和暂时性死区

  • 既然可以定义默认参数,默认参数甚至可以动态调用函数,那么他就一定有一个作用域,默认参数的定义效果和下面第二个代码是一样的
let Fun1 = function (a = 123, b = 456) {
    console.log(a, b)
}
function Fun2() {
    
let a = 123
    let b = 456

}

这里传达两个信息: 第一作用域是函数作用域,第二默认参数的定义具有顺序,所以后面的参数可以引用前面的参数,但是前面的不能引用后面的参数。这就是暂时性死区

function Fun4(a = b + 1, b = 1) {
    console.log(a, b)
}
Fun4()//Uncaught ReferenceError ReferenceError: Cannot access 'b' before initialization

默认参数只有在函数被调用而且没有传入参数的时候才会求值

arguments 动态参数

arguments只存在函数里面 实际上是一个伪数组

funtion getSum()
{
                for( let i = 0 ; i < arguments.length;i++)
        {
        sum += arguments[i]
        }
}
getSum(1,1,2,2,31,4)
getSum(1,2,3)

arguments在箭头箭头函数里没有 而且这是个伪数组 他的方法特殊的

  • 修改argument的元素,对应形参的值也会发生改变
  • 如果形参没有被传入,那么给argument对应元素复制无用
  • 在严格模式中,修改argument不能反映到形参上
  • argument不反映默认参数,只会反映传给函数的参数

rest 剩余参数

…arr 这个真数组他只接收形参以外的东西 , 必须要放在最后

funtion getSum (a,b,...list)
{
                console.log(list)//不用写...
}

对象中的rest

function connect({ host, port, ...user }) {
            console.log(host)
            console.log(port)
            console.log(user)
        }
        connect({
            host: '127.0.0.1',
            port: 3306,
            username: 'root',
            password: 'root',
            type: 'master'
        })

对象中的展开运算符

做对象的合并

const a = {
            q: "a"
        }
        const b = {
            w: "b"
        }
        const c = {
            e: "c"
        }
        const d = { ...a, ...b, ...c }
        console.log(d)//{q: 'a', w: 'b', e: 'c'}

⚠️区分展开运算符

不一定在函数里的

const arr = [1,2,3]
console.log(...arr) // 1 2 3
//...arr === 1,2,3

不会修改数组

应用:求最大值

console.log(Math.arr(..arr)) //3 Math.arr(不能传数组)

应用:合并数组

const arr2 = [3,4,5]
const arr3 = [...arr,...arr2]// [1,2,3,3,4,5]

箭头函数

箭头函数没有arguments变量,super(),new.target 箭头函数不能作为构造实例化对象 箭头函数的this是静态的,它的this就是声明的时候的this 箭头函数省略花括号之后要省略return 箭头函数没有prototype属性

基本语法

!important 目的是为了更短的代码,把函数表达式更加简洁

const fn = funtion() {
console.log(123)}
//可以等价于=>
const fn = 
(x) =>
 {
console.log(123)}

案例:

cosnt arr = [1,6,7,11,2,90]
cosnt result = arr.filter(item => item % 2 === 0 )

省略

//**'**这个小括号在只有一个参数的时候可以省略'
const fn = x => console.log(123)//只有一行代码的时候大括号可以省略
const fn = x => x + x //省略了return
console.log(fn(1))//2

直接返回对象

用小括号包含对象,因为函数体也是{}

const fn = (uname) => ({name:uname})//用小括号包含起来

箭头函数参数

没有arguments 但是有…arr剩余参数

const getSum = (...arr) => {
let sum =  0
for ( let i = 0 ; i < arr.lenght; i++
sum += arr[i]
return result
}

this

this就是调用此函数的对象

对于普通情况而言:

console.log(this) //window
function fn() {
console.log(this)//window
}
//对象方法中的this
const obj = {
name : 'andy',
sayHi: function() {
console.log(this)//得到this==obj
}
obj.sayHi

对于箭头函数而言

箭头函数不会创this,只会从作用域链的上一层来找this 所以在dom中用回调函数的时候就不用箭头函数(丢失this 只会指向window)

const fn = () => {
console.log(this)//在这个局部作用域中没有this,
//所以找到的是window,实际上不是window调用的
}
//对象方法的箭头函数的this
const obj = {
name : 'andy',
sayHi : () => {
console.log(this)//这个作用域里没有this,所以找到的this也是window
}
obj.sayHi

处理this

严格模式下没有调用调用主体的时候this = undefine

普通函数

谁调用就指向谁

箭头函数

箭头函数会找最近的外层的this,本身不会产生this

操作dom对象的时候要用this就不要用箭头函数

再构造函数和原型函数中也不要用箭头函数

改变this

call()

调用的同时改变this指向 fun.call(thisArg,arg1,arg2)

funciton fn() {
console.log(this)
}
fn.call(obj)//输出obj
apply()

类似call fun.apply(thisArg,[argArr])

function fn (x, y){
console.log(this)
console.log(x+y)
}
fn.apply(obj,[1,2])

使用场景:求最大值

//const max = Math.max(...arr)
const max = Math.max.apply(Math,arr)
const max = Math.max.apply(null,arr)
bind()

bind()不会调用函数 语法和call()相似

function fn()  {
        console.log(this)
}
const newFn fn.bind(obj)//返回值是一个函数(原函数的拷贝)

函数重载

在比如C++和java的语言中,函数可以重载,就是在同一个定义域里可以定义同名的不同函数,这些函数的参数和实现不同,调用这个函数的时候,会进行重载决策,决定用哪个函数 JavaScript中不存在函数重载,因为参数本来就是一个参数数组的形式传进去的,所以后声明的函数会直接覆盖原来的同名函数

函数解耦(decoupling)

arguments.callee()

调用创造arguments所在作用域的那个函数

  • 递归函数原本是高度耦合的,函数名是一样的才行
function coupling(n) {
    //这是一个硬代码递归函数
    if (n > 10) {
        console.log(n * 10)
    }
    else {
        coupling(n + 1)//这里执行递归必须是同名函数
    }
}
coupling(1)
coupling(12)

解耦之后,就算名字改变了也无所谓了

function coupling(n) {
    //这是一个硬代码递归函数
    if (n > 10) {
        console.log(n * 10)
    }
    else {
        
arguments.callee(n + 1)//解耦

}
let decoupling = coupling;//将coupling指向的函数赋值给decoupling,此时函数名为decouping,函数内容不变
//然后把原来的指针指向其他地方
coupling = function (n) {
    console.log(0)
}
decoupling(1)//110
coupling(1)//0

这个方法在严格模式下不能使用

在严格模式下要使用命名函数表达式

const Fun = (function f(num){}) 在参数括号前面加一个f

const Fun = function f(num) {
    if (num > 10) {
        console.log(num)
    }
    else {
        f(num + 1)
    }
}

函数内置属性方法

function.caller

引用调用当前函数的函数(的源代码)

  • 如果在全局中调用就是null

  • 可以结合函数解耦降低耦合度

    • arguments.callee.caller

function.name

函数对象暴露了一个只读的name 属性

用箭头函数,函数声明,函数表达式创建的函数都会有这个name属性,如果没有名字那就是空字符串

如果使用bind实例化的函数或者是set() | get() 函数会加上一个前缀 : bound | set | get

new.target()

函数内部调用new.target() 可以确定函数是不是由new关键字创建的,如果不是就是undefine,否则是被调用的构造函数

function.prototype

不可枚举,所以不被for in读取

function.length

函数的形参个数

function.call(thisArg,arg1,arg2...)

function.apply(thisArg,argumentsList()

function.bond(this)

不会调用函数,直接拷贝一个函数,指定this

尾调用优化

尾调用

最常见的就是递归函数,每次返回另一个函数的调用

尾调用优化的要求

  • 严格模式

    • 因为非严格模式下有funcition.arguments | function.caller 他们会调用外部函数的栈帧,意味着就不能优化了
  • 外部函数的返回是内部函数的调用

  • 返回之后不用再做其他操作

  • 尾调用的函数不是一个闭包

未优化尾调用

  1. 执行外层函数,推入第一个栈帧
  2. 执行到return发现必须对内层函数求值
  3. 执行内层函数,推入第二个栈帧
  4. return第二个函数,第一个函数得到值返回
  5. 弹出栈帧

使用了两个栈帧

优化后尾调用

  1. 执行外层函数,推入第一个栈帧
  2. 执行到return发现必须对内层求值
  3. 引擎发现外层函数的返回值就是内层函数的调用,直接弹出第一个栈帧
  4. 执行第二个函数,推入第二个栈帧
  5. 计算返回值
  6. 弹出第二个栈帧

全程只用了一个栈帧,在递归中的优化理论上会比较明显


代理和反射

Proxy对象,创建一个新的代理,拦截 和自定义基本操作(查找,赋值,枚举,调用等操作)

  • new Proxy()创建一个新代理

    • const p = new Proxy (target , handler) target要代理的对象,handler处理函数
    • handler中包含Proxy 的各个捕获器,trap,所有捕获器都是可选的,可以重写或者修改
    • [handler.getPrototypeOf()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getPrototypeOf>)[Object.getPrototypeOf](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf>) 方法的捕捉器。[handler.setPrototypeOf()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/setPrototypeOf>)[Object.setPrototypeOf](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf>) 方法的捕捉器。[handler.isExtensible()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/isExtensible>)[Object.isExtensible](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible>) 方法的捕捉器。[handler.preventExtensions()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/preventExtensions>)[Object.preventExtensions](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions>) 方法的捕捉器。[handler.getOwnPropertyDescriptor()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getOwnPropertyDescriptor>)[Object.getOwnPropertyDescriptor](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor>) 方法的捕捉器。[handler.defineProperty()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/defineProperty>)[Object.defineProperty](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty>) 方法的捕捉器。[handler.has()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/has>)[in](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/in>) 操作符的捕捉器。[handler.get()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get>)属性读取操作的捕捉器。[handler.set()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set>)属性设置操作的捕捉器。[handler.deleteProperty()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty>)[delete](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/delete>) 操作符的捕捉器。[handler.ownKeys()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys>)[Object.getOwnPropertyNames](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames>) 方法和 [Object.getOwnPropertySymbols](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols>) 方法的捕捉器。[handler.apply()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply>)函数调用操作的捕捉器。[handler.construct()](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/construct>)[new](<https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new>) 操作符的捕捉器。
    • get() 读取操作的捕获器接受3个参数 tarpTarget目标对象,porperty被读取的属性,reveiver代理对象
    •   class Running {
            age = 18
            distance = '100km'
        }
      
        const handler = {
            get(target, property, recevier) {
                let result = target[property] + '(For reference)'
                return result
            }
        }
      
        const mike = new Running()
        const proxy = new Proxy(mike, handler)
        console.log(mike.age)//18
        console.log(proxy.age)//18(For reference)
      
  • 在代理和目标上的赋值和修改都会反映到对方身上,因为他们操作的其实是同一个对象

捕获器不变式

防止捕获器返回一个反常的值,比如在不可写不可配置的属性上返回不同的值,会抛出TypeError

可撤销代理

  • 通过静态方法实现可撤销代理

  • Proxy.revocable ( target , handler )

    • 返回一个对象,对象的结构为 : {"proxy": proxy, "revoke": revoke}
    • 用解构赋值来定义: const {proxy , revoke } = Proxy.revocable (target ,handler)

多层代理

class Running {
    age = 18
    distance = '100km'
}
const handler2 = {
    get(target, property, recevier) {
        let result = 
Reflect.get(...arguments)
 + '(Protect for the second time)'
        return result//这里的Reflect拿到的已经是代理过一次的了
    }
}
const handler1 = {
    get(target, property, recevier) {
        let result = 
Reflect.get(...arguments)
 + '(Protect for the first time)'
        return result//这个Reflect出来还是原始的
    }
}
const mike = new Running()

const proxy2 = new Proxy(mike, handler2)
const proxy1 = new Proxy(proxy2, handler1)

console.log(mike.age)//18
console.log(proxy1.age)//18(Protect for the second time)(Protect for the first time)

代理的问题

代理改变了this,对于依赖this的对象,比如用weakmap实现的私有属性,用代理就会无法获取

const privateClass = (function () {
    let priDate = new WeakMap()
    class Private {
        constructor(id) {
            priDate.set(this, id)
        }
        set id(id) {//set基本方法,当一个关键字,id才是函数的名字
            priDate.set(this, id)
        }
        get id() {//get基本方法,这里重名了,但这里要当作一个属性来用
            return priDate.get(this)
        }
    }
    return Private
})()//立刻执行函数,得到一个对象,封装了一个类的
const test = new privateClass(124)
console.log(test.id)//124
const proxy = new Proxy(test, {})//建立空代理
console.log(proxy.id)//undefined

此外,还有Date类型依赖this值上的[[NumberDate]]但是代理无法控制这个值,所以也会导致TypeError

反射 API

要重写一个基本操作不是都像get的实现这样简单,所以全部手撕不可取,使用Reflect 对象来轻松重建,Reflect封装了基本操作

  • Reflect.trap(...arguments)
const handler = {
    get(target, property, recevier) {
        
let result = Reflect.get(...arguments) + '(For reference)'

        return result
    }
}

效果和上面是一样的,只不过是用Reflect.get(...argument)代替了我们手动的实现的获取的返回值

  • 状态标记,有点反射方法提供了一个标记,用来表示意图执行的操作是否成功

    • Reflect.defineProperty() | .prevneExtensions() | setPrototypeOf() | set() | deleteProerty()
  • 代替操作符,

    • Reflect.get() 访问符
    • Reflect.set() 赋值符
    • Reflect.has() in|with
    • Reflect.deleteProperty() delete符
    • Reflect.construct() new符

代理模式

追踪属性访问

get(target,property,recevier)

//追踪属性访问
const test1 = {
    username: 'abc',
    age: 18
}
const proxy1 = new Proxy(test1, {
    get(target, property, recevier) {
        console.log(
get ${target.username}'s property: ${property}
)
        return Reflect.get(...arguments)
    },
    set(target, property, value, recevier) {
        console.log(
setting ${target.username}'s ${property} as ${value}
)
        Reflect.set(...arguments)
    }
})
proxy1.username
proxy1.age = 20
console.log(test1.age)
隐藏属性

get(target,property,reveicier)

has(target,property)

//隐藏属性
const hiddenList = ['a', 'b']
const test2 = {
    'a': 1,
    'b': 2,
    'c': 3
}
const proxy2 = new Proxy(test2, {
    get(target, property, recevier) {
        if (hiddenList.includes(property)) {
            return undefined
        }
        else return Reflect.get(...arguments)
    },
    has(target, property) {
        if (hiddenList.includes(property)) {
            return false
        }
        else return Reflect.has(...arguments)
    }
})
console.log(proxy2.a)
console.log(test2.a)
console.log('b' in proxy2)//false
console.log('b' in test2)//true
验证属性

set(target,property,value)

//属性验证
const test3 = {
}//必须是数字类型
const proxy3 = new Proxy(test3, {
    set(target, property, value, recevier) {
        if (typeof value !== 'number') {
            console.log('setting faliure')
        }
        else {
            Reflect.set(...arguments)
            return Reflect.set(...arguments)
        }
    }
})
proxy3.a = 1
proxy3.b = '1'//setting faliure
console.log(proxy3)//Proxy {a: 1}
函数和构造函数参数验证

apply(target,thisArg,argumentsList)

constructor(target,argumentList,newTarget)

//构造函数参数验证
class Test4 {
    constructor(id) {
        this.id = id
    }
}
const proxy4 = new Proxy(Test4, {
    construct(target, argumentList, newTarget) {
        if (typeof argumentList[0] !== 'number') {
            throw 'the argument must be a number'//这里必须抛错误,因为有捕获不变式的限制
        }
        else {
            return Reflect.construct(...arguments)
        }
    }
})
// const test4 = new proxy4('123') //Error the argument must be a number
const test4_ = new proxy4(123)
console.log(test4_)//Test4 {id: 123}
//函数参数验证
function test5(id) {
    console.log(id)
}
const proxy5 = new Proxy(test5, {
    apply(target, thisArg, argumentList) {
        if (typeof argumentList[0] !== 'number') {
            throw 'argument must be type of number'
        }
        else {
            return Reflect.apply(...arguments)
        }
    }
})
// proxy5('123')//Uncaught Error argument must be type of number
proxy5(134)
数据绑定和可视化对象

就是在set的时候将对象加入到一个全局的数组中

部分插图来自网络,侵删。 小弟才疏学浅难免错漏,请多多担待不吝赐教。