# 1 ECMAScrity 与 JavaScript 的区别

  • ECMAScript 语言层面,规范

  • javascript 实现了ecmaScript的语言的标准

  • JavaScript= dom + bom + ecmaScript

  • ECMAScript 2015(ES2015)

# 2 作用域: 代码中的某个成员能够起作用的范围

  • 全局作用域

  • 函数作用域

  • 块级作用域 es6中新增的块级作用域,一堆花括号之间形成的作用域 如:

  if (true) {
    console.log('aaa')
  }

  for(var i = 0; i < 10; i++) {
    console.log('aaa')
  }

以前块是没有独立的作用域的,导致我们在块中定义的作用域,外面也能访问到

  if (true) {
    var foo = 'aaa'
  }
  console.log(foo)

es6中可以使用 let const 建立块级作用域

  for(var i = 0; i < 3; i++) {
    for(var i = 0; i < 3; i++) {
      console.log(i)
    }
    console.log('内层是i3') // 不满如循环条件
  }

只打印3次是因为用var声明的i,不是块级作用域,是全局变量,外层i声明完以后,内层在声明一次, 解决全局变量重名的问题

  var elements = [{}, {}, {}]
  for (var i = 0; i < elements.length; i++) {
    elements[i].onclick = function() {
      console.log(i)
    }
  }
  elements[2].onclick() // 都会打印3

因为i是全局变量,所以自循环完以后是3,无论什么时候打印都是3

  var elements = [{}, {}, {}]
  for (var i = 0; i < elements.length; i++) {
    elements[i].onclick = (function(i) {
      console.log(i)
    })(i)
  }
  elements[2].onclick() // 都会打印3

可以使用闭包来解决这个问题,闭包借助函数作用域来摆脱全局作用域所产生的的影响

  var elements = [{}, {}, {}]
  for (let i = 0; i < elements.length; i++) {
    elements[i].onclick = function() {
      console.log(i)
    }
  }
  elements[2].onclick() // 都会打印3

使用let 创建块级作用域

let 不会出现变量提升

# 1、 const对象的属性可以修改吗?

const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。

但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。

# 2、 如果new一个箭头函数的会怎么样

箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。

new操作符的实现步骤如下:

  • 创建一个对象
  • 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
  • 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)
  • 返回新的对象

所以,上面的第二、三步,箭头函数都是没有办法执行的。

# 3、箭头函数与普通函数的区别

# (1)、箭头函数比普通函数更加简洁

  • 如果没有参数,就直接写一个空括号即可
  • 如果只有一个参数,可以省去参数的括号
  • 如果有多个参数,用逗号分割
  • 如果函数体的返回值只有一句,可以省略大括号
  • 如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常见的就是调用一个函数:
  let fn = () => void doesNotReturn();

# (2)、箭头函数没有自己的this

箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。

# (3)、箭头函数继承来的this指向永远不会改变

  var id = 'GLOBAL';
  var obj = {
    id: 'OBJ',
    a: function(){
      console.log(this.id);
    },
    b: () => {
      console.log(this.id);
    }
  };
  obj.a();    // 'OBJ'
  obj.b();    // 'GLOBAL'
  new obj.a()  // undefined
  new obj.b()  // Uncaught TypeError: obj.b is not a constructor

对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。需要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。

# (4)call()、apply()、bind()等方法不能改变箭头函数中this的指向

  var id = 'Global';
  let fun1 = () => {
      console.log(this.id)
  };
  fun1();                     // 'Global'
  fun1.call({id: 'Obj'});     // 'Global'
  fun1.apply({id: 'Obj'});    // 'Global'
  fun1.bind({id: 'Obj'})();   // 'Global'

# (5) 箭头函数不能作为构造函数使用

构造函数在new的步骤在上面已经说过了,实际上第二步就是将函数中的this指向该对象。 但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。

# (6) 箭头函数没有自己的arguments

箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。

# (7)箭头函数没有prototype

# (8) 箭头函数不能用作Generator函数,不能使用yeild关键字

# 4、箭头函数的this指向哪⾥?

箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。

可以⽤Babel理解⼀下箭头函数:

  // ES6 
  const obj = { 
    getArrow() { 
      return () => { 
        console.log(this === obj); 
      }; 
    } 
  }

转化后:

  // ES5,由 Babel 转译
  var obj = { 
    getArrow: function getArrow() { 
      var _this = this; 
      return function () { 
          console.log(_this === obj); 
      }; 
    } 
  };

# 3 对象解构


const obj =  { name: 'cyz', age: 18 }
const { name : objName = 'jack' } = obj  // 当有重名时,objName 为新的名字
console.log(objName)  ==> 'cyz'
const { log } = console

log(123)

如何提取高度嵌套的对象里的指定属性? 有时会遇到一些嵌套程度非常深的对象:

  const school = {
    classes: {
        stu: {
          name: 'Bob',
          age: 24,
        }
    }
  }

像此处的 name 这个变量,嵌套了四层,此时如果仍然尝试老方法来提取它:

  const { name } = school

显然是不奏效的,因为 school 这个对象本身是没有 name 这个属性的,name 位于 school 对象的“儿子的儿子”对象里面。要想把 name 提取出来,一种比较笨的方法是逐层解构:

  const { classes } = school
  const { stu } = classes
  const { name } = stu
  name // 'Bob'

但是还有一种更标准的做法,可以用一行代码来解决这个问题:

  const { classes: { stu: { name } }} = school
       
  console.log(name)  // 'Bob'

可以在解构出来的变量名右侧,通过冒号+{目标属性名}这种形式,进一步解构它,一直解构到拿到目标数据为止。

# 4 带标签的模板字符串


// 模板字符串 带标签的模板字符串
// console.log(123)
const name = 'tom'
const gender = true

function myTagFunc(strings, name, gender) {
  // console.log(strings, name, gender)
  const sex = gender ? 'man' : 'woman'
  return strings[0] + name + strings[1] + sex + strings[2]
}
const result = myTagFunc`hey,${name} is a ${gender}`
hey,tom is a man

# 5 字符串的扩展方法


const message = 'Error: foo id not defined'

console.log(
    message.startWith('Error')  // true
    message.endsWith('defined')    // true
    message.includes('foo')        // true
)

# 6 参数默认值


//  带参数默认值的参数在后面,避免使用短路运算来设置默认值

//(默认值应为参数为undefined的时候设置,规避false也使用参数默认值)

function foo (bar, enable = true) {

    console.log('foo invoked - enable: ')

    console.log(enable)

}

foo(false)

# 7 剩余参数


//  ... 展开剩余参数 只能在形参的最后一位,只能使用一次

function foo(...args) {
  console.log(args) // [ 1, 2, 3, 4 ]
}
foo(1,2,3,4)

扩展运算符被用在函数形参上时,它还可以把一个分离的参数序列整合成一个数组

  function mutiple(...args) {
    let result = 1;
    for (var val of args) {
      result *= val;
    }
    return result;
  }
  mutiple(1, 2, 3, 4) // 24

这里,传入 mutiple 的是四个分离的参数,但是如果在 mutiple 函数里尝试输出 args 的值,会发现它是一个数组:

  function mutiple(...args) {
    console.log(args)
  }
  mutiple(1, 2, 3, 4) // [1, 2, 3, 4]

这就是 … rest运算符的又一层威力了,它可以把函数的多个入参收敛进一个数组里。这一点经常用于获取函数的多余参数,或者像上面这样处理函数参数个数不确定的情况。

# 8 展开运算符

# (1) 对象扩展运算符

对象的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。

  let bar = { a: 1, b: 2 };
  let baz = { ...bar }; // { a: 1, b: 2 }

上述方法实际上等价于:

  let bar = { a: 1, b: 2 };
  let baz = Object.assign({}, bar); // { a: 1, b: 2 }

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)。

同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。

  let bar = {a: 1, b: 2};
  let baz = {...bar, ...{a:2, b: 4}};  // {a: 2, b: 4}

利用上述特性就可以很方便的修改对象的部分属性。在redux中的reducer函数规定必须是一个纯函数,reducer中的state对象要求不能直接修改,可以通过扩展运算符把修改路径的对象都复制一遍,然后产生一个新的对象返回。

需要注意:扩展运算符对对象实例的拷贝属于浅拷贝。

# (1)数组扩展运算符

    const arr = ['foo', 'bar', 'baz']
    console.log.apply(console, arr)
    console.log(...arr)  // 'foo' 'bar' 'baz'

下面是数组扩展运算符符的应用:

  • 将数组转换为参数序列
  function add(x, y) {
    return x + y;
  }
  const numbers = [1, 2];
  add(...numbers) // 3
  • 复制数组
  const arr1 = [1, 2];
  const arr2 = [...arr1];

要记住:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中,这里参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。

  • 合并数组 如果想在数组内合并数组,可以这样:
  const arr1 = ['two', 'three'];
  const arr2 = ['one', ...arr1, 'four', 'five'];
  // ["one", "two", "three", "four", "five"]
  • 扩展运算符与解构赋值结合起来,用于生成数组
  const [first, ...rest] = [1, 2, 3, 4, 5];
  first // 1
  rest  // [2, 3, 4, 5]

需要注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

  const [...rest, last] = [1, 2, 3, 4, 5];         // 报错
  const [first, ...rest, last] = [1, 2, 3, 4, 5];  // 报错
  • 将字符串转为真正的数组
  [...'hello']    // [ "h", "e", "l", "l", "o" ]
  • 任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组 比较常见的应用是可以将某些数据结构转为数组:
  // arguments对象
  function foo() {
    const args = [...arguments];
  }

用于替换es5中的Array.prototype.slice.call(arguments)写法。

  • 使用Math函数获取数组中特定的值
  const numbers = [9, 4, 7, 1];
  Math.min(...numbers); // 1
  Math.max(...numbers); // 9

# 9 箭头函数

不会改变this执行,始终都是当前作用域的this
  const inc = n => n + 1

  const person = {
    name: 'tom',
    say: function() {
      console.log(this.name)  // tom
    }
    ai: () => {
      console.log(this.this) // undefined
    }
  }
  

# 10 对象字面量

const obj = {
  foo: 123,
  bar,
  method1() {
    console.log(22)
  },
  method1: function() {
    // 和上面的写法是等价的
  }// 计算属性名   es6中可以动态的去屑计算属性名
  [bar]: 123,
  [a + b]: 222,
  [Math.random()]: 123,
}
obj[Math.random] = 123
 

# 11 Object.assign()


const source1 = {
  a: 123,
  b: 123,
}

const target = {
  a: 456,
  c: 456,
}

const result = Object.assign(target, source1) // target目标对象
console.log(target)             // {a: 123, b: 456, c: 343}
console.log(result === target)  // true
// 将source1对象覆盖前面target对象,并返回前面的那个对象
Object.is()
console.log(Object.is(NaN, NaN))

# 12 proxy


const person = {
    name: 'cuy',
    age: 10
}

const personProxy = new Proxy(person, {
    get(target, property){
      return property in target ? target[property] : 'default'
    },
    set(target, property, value){
      if(property === 'age'){
        if(!Number.isInteger(value)){
            throw new TypeError(`${value} is not an int`)
        }
      }
    console.log(target, property, value)
    }
})
// get 取值时触发的方法 target: 设置的对象 property: 设置的属性值
// set 设置值时厨房的方法 target: 对象 property: 属性值 value: 设置的值
    

# 13 Proxy vs Object.defineProperty

  • defineProperty 只能监视属性的读写,特意的方式去监视对象
  • Proxy能够监视到更多的对象操作,非入侵的方式去监视对象

const person = {
  name: 'cuy',
  age: 10
}

const personProxy = new Proxy(person, {
    deleteProperty(target, property) { // 代理对象,删除的属性名
        console.log('delete', property)
        delete target[property]
    },
})

handler方法 触发方式
get 读取某个属性
set 写入某个属性
has in操作符
deleteProperty delete 操作符
getProperty Object.getPropertypeOf()
setProperty Object.setPropertypeOf()
isExtensible Object.isExtensible()
preventExtensions Object.preventExtensions()
getOwnPropertyDescriptor Object.getOwnPropertyDescriptor()
defineProperty Object.defineProperty()
ownKeys Object.keys()、 Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()
apply 调用一个函数
construct 用new调用一个函数
  • 数组方面的监视

# 14 reflect 静态类,内部封装了一些列对对象的底层操作,

    统一提供了一套用于操作对象的API

Reflect.get()

const obj = {
    foo: '123',
    bar: '456',
}
const proxy = new Proxy(obj, {
    get( target, property){
        console.log('watch logic~')
        return Reflect.get(target, property)
    }
})

console.log("name' in obj)        ===   console.log(Reflect.has(obj, 'name'))

console.log(delete obj['age']     ===   console.log(Reflect.deleteProperty(obj, 'age'))

console.log(Object.keys(obj))    ===    console.log(Reflect.ownKeys(obj))
// 更多方法见mdn

# 15 Promise 一种更优的异步编程解决方案,解决了异步编程回调函数嵌套过深的问题

# 16 class 类


function Person (name) {
    this.name = name
}

Person.prototype.say = function () {
    console.log(`hi my name id ${this.name}`)
}

class Person {
    constructor (name) {
        this.name = name
    }
    say() {
        console.log(`hi, my name is ${this.name}`)
    }
    static creat (name) {
      return new Person(name)
    }
}
const p = new Person('tom')
p.say()

# 17 静态方法 static

    见上面代码块的static

# 18 extends 继承


class Person {
    constructor (name) {
        this.name = name
    }
    say () {
        console.log(`hi, my name is ${this.name}`)
    }
}

class Student extends Person {
  constructor (name, number) {
    super(name)
    this.number = number
  }
  hello () {
    super.say()
    console.log(`my school number is ${this.number}`)
  }
}

const s = new Student('jack', '100')
s.hello()

# 19 set数据结构(集合)

不允许重复,一种数据类型
const s = new Set()
s.add(1).add(2).add(3)
s.forEach(i => console.log(i))

s.has()  // 是否包含某个值
s.delete(2) // 删除某一项
s.clear()  // 清空
const = [...s] // 展开某个数组

# 20 map (类似对象) 结构的键只能是 字符串类型

const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{a:1}] = 'value'

console.log(Object.keys(obj)) // 打印对象中的键 都会转换为字符串 就会有问题

m.set(tom, 90)
console.log(m) // 不会转换

m.forEach((value, key) => {
  console.log(value, key)
})

# 21 Symbol 符号 原始的数据类型

+ 独一无二的,为对象添加一个独一无二的数据类型
+ es2019  7种数据类型 number string boolean undefined  null array  NaN object  BigInt
  const symbol1 = Symbol();
  const symbol2 = Symbol(42);
  const symbol3 = Symbol('foo');

  console.log(typeof symbol1);
  // expected output: "symbol"

  console.log(symbol2 === 42);
  // expected output: false

  console.log(symbol3.toString());
  // expected output: "Symbol(foo)"

  console.log(Symbol('foo') === Symbol('foo'));
  // expected output: false

# 22 for---of

  • for 循环 遍历数组
  • for ... in 遍历键值对
  • forEach map
  //for of 编辑任意一种结构
  const a = [1, 2, 3]
  for (const item of a) {
    if(item > 100){
      break // 终止循环
    }
  }
  // 伪数组 
  const s = new Set(['foo', 'bar'])
  for(const item if s) {
    console.log(item)
  }

  const m = new Map()
  m.set('foo', '123')
  m.set('var', '345')
  for (const [key, value] of m) {

  }

  const obj = {foo: 123, bar: 456}
  for (const item of obj) {
    console.log(item)
  }  // 无法遍历 报错

  • Iterable Iterator IteratorResult 接口 可迭代接口
// 实现可迭代接口(Iterable)
const obj = {
  [Symbol.iterator]: function () {
    return {
      next: function () {
        return {
          value: 'zce',
          done: true
        }
      }
    }
  }
}
// 给obj添加迭代方法 但是并不能循环
const obj = {
  store: ['foo', 'bar', 'baz']

  [Symbol.iterator]: function () {
    let index = 0
    const self = this

    return {
      next: function () {
        return {
          value: self.store[index],
          done: index >= self.store.length
        }
        index ++ 
        return result
      } 
    }
  }
}

// 此时obj 可使用for of 循环

  • 迭代器模式 对外提供统一遍历接口 ,不用关心内部的数据结构是什么样的
  const todos = {
    left: ['吃饭', '睡觉', '打豆豆'],
    learn: ['语文', '数学', '外语'],
    work: ['喝茶'],
    each: function (callback) {
      const all = [].concat(this.life, this.learn, this.work)
      for (const item of all) {
        callback(item)
      }
    },

    [Symbol.iterator]: function() {
      const all = [...this.life, ...this.learn, ...this.work]
      let index = 0
      return {
        next: function() {
          return {
            value: all[index],
            done: index++ >= all.length
          }
        }
      }
    }
  }

  todo.each(function(item) {
    console.log(item)
  })
  console.log('............')
  for(const item of todos) {
    console.log(item)
  }

# 23 es2015 生成器 Generator

  • 作用: 避免异步编程中回调嵌套过深,提供更好的异步编程方案
    function * foo () {
      console.log('zec')
      return 100
    }
    
    const result = foo()
    console.log(result) // object [Generator] {}
    console.log(result.next()) // zce {value : 100, done: true}
    // next() 后才开始执行 
    
    function * foo () {
      console.log('1111')
      yield 100
      console.log('2222')
      yield 200
      console.log('3333')
      yield 300
    }
    
    const generator = foo()
    // 惰性执行 yield会暂停函数的执行,并将yield后面的值返回给 generator方法 每执行一次next() 执行一次
    // 生成器方法
    console.log(genetator.next())
    console.log(genetator.next())
    console.log(genetator.next())
    console.log(genetator.next()) // value undefined 
    
    
  • 生成器应用
      // 发号器
      function * creatIdMaker () {
        let id = 1
        while (true) {
          yield id++
        }
      }
    
      const idMaker = createIdMaker()
    
      console.log(idMaker.next().value)
      console.log(idMaker.next().value)
      console.log(idMaker.next().value)
      console.log(idMaker.next().value)
    
    

# ES 2016 概述

includes 判断数组中的值是否存在 indexof 不能查找NaN
Math.pow(2, 10)  2的指数 2的10次方
console.log(2 ** 10)

# ES 2017

  • Object.values(obj) // obj中值的数据 Object.keys() // obj中键的数组

  • Object.entries() // Obj中的键值以数据的形式遍历

    for( const[key, value] of Object.entries(obj) {
      console.log(key, value)
    })
    
  • 字符串填充方法padEnd padStart 填充导零

      // 用给定的字符串去填充目标字符串的开始或结束位置,知道字符串达到制定长度 为止
      const books = {
        html: 5,
        css: 15,
        javascript: 124
      }
    
      for(const [name, count] of Object.entries(books)) {
        console.log(name, count)
      }
    
      for (const [name, count] of Object.entries(books)) {
        console.log(`${name.pad(16, '_')|${count.toSring().padStart(3, '0')}}`)
      }
    
    
    html---------|005
    css----------|015
    javascript---|124
    
  • 伪逗号 函数参数添加伪逗号

  • Async / Await