ES6学习笔记(上)

362 阅读25分钟

一、四种作用域

  1. 全局作用域
  2. 函数作用域
  3. 块状作用域 ,配合 let 和 const 使用
  4. 动态作用域

1.全局作用域

  1. ==var 声明的变量为全局变量==不能被删除
  2. 没有 var 定义的变量是作为 window 的属性定义的,因为 window 为全局对象 所以看上去它也具有全局属性,这是和window有关而不是因为它本身为一个 全局变量
  3. 在函数内部 没有用 var 定义的变量,它不是具备 函数作用域 而是具备全局作用域的

后两个都不是 ==全局变量== 都是作为 window的属性 存在的

2.函数作用域

  1. 函数作用域 = 局部作用域
  2. 在函数内部定义的变量,就是局部作用域。函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域!
function bar() {
      var testValue = 'inner';
    }
    
    console.log(testValue);		// 报错:ReferenceError: testValue is not defined

如果想读取函数内的变量,必须借助 return 或者闭包。

function bar(value) {
      var testValue = 'inner';
    
      return testValue + value;
    }
    
    console.log(bar('fun'));		// "innerfun"

这是借助 return 的方式,下面是闭包的方式:

function bar(value) {
      var testValue = 'inner';
    
      var rusult = testValue + value;
    
      function innser() {
         return rusult;
      };
    
      return innser();
    }
    
    console.log(bar('fun'));		// "innerfun"

如果一个==变量 或者 其它表达式 不在“当前得作用域”==,那么JavaScript机制会继续==沿着作用域链向上查找==直到找到全局作用域,通常是指沿着链式的作用域查找,而不能从父作用域引用子作用域中的变量和引用-----作用域链

3.块状作用域 (let 和 const)

==注意: var 定义的变量会变量提升== 而let和const不会,所以var不能进行块状作用域的定义,会让变量进行提升

4.动态作用域

  1. this 具有动态指向 通过bind的使用

二、let && const

let

  1. let 声明的变量拥有块级作用域
  2. let 声明的全局变量不是全局对象的属性

这就意味着,你不可以通过 window.变量名 的方式访问这些变量,而 var 声明的全局变量是 window 的属性,是可以通过 window.变量名 的方式访问的。

  1. let不能重定义变量
  2. let声明的变量不会进行变量提升

const 声明的特点

  1. 只能赋值一次
  2. 初始化时一定要赋值
  3. 块状作用域
  4. const 声明的全局变量不是全局对象的属性
  5. 不能重复定义
  6. 不会变量提升

三、数组 Array 遍历方法

ES5 中遍历数组的方法

  1. for 循环
const arr = [1, 2, 3, 4, 5]
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i])
}
  1. forEach

不支持 break、continue

// forEach 不允许使用 break continue
arr.forEach(function (item) {
  console.log(item)
})
  1. every (不支持continue 和 break 但可以实现类似效果)

使用 every 遍历就可以做到 break 那样的效果,简单的说 return false 等同于 break,return true 等同于 continue。如果不写,默认是 return false。

arr.every(function (item) {
  if (item === 2) {
    return false // 达到break的效果
  }
  console.log(item)
  return true
})
  1. for in (数组也是一个对象) 其中的 ==index 为字符串== for…in 确实可以遍历数组,而且还支持 continue、break等功能,但是它真的没有瑕疵吗?==如果 array 有自定义属性,你发现也会被遍历出来(显然不合理)== 这是因为 for…in 是为遍历对象创造的({a:1,b:2}),不是为数组设计的。
arr.a = 8
for (let index in arr) {
  console.log(index, arr[index])
}

// 注意:for…in代码块中不能有 return,不然会抛出异常。

ES6中遍历数组的方法

for of

==for..of适用遍历数/数组对象/字符串/map/set等拥有迭代器对象的集合.但是不能遍历对象,因为没有迭代器对象.==

for (variable of iterable) {
        
    }    
// 这个伪代码,of 后面是 iterable 既不是 for 循环规定的 array,也不是 for…in 规定的 Object,而是 iterable。
//  即 for…of 遍历的是一切可遍历的元素(数组、对象、集合)等,不要小瞧这个功能,因为在 ES6 中允许开发者 自定义遍历,换句话说任何数据结构都可以自定义一个遍历,这个遍历是不能被 for、for…in 理解和实现的

四、伪数组转化为数组

数组是开发中经常用到的数据结构,它非常好用。在 JavaScript 的世界里有些对象被理解为数组,然而却不能使用数组的原生 API,比如==函数中的 arguments、DOM中的 NodeList等==当然,还有一些可遍历的对象,看上去都像数组却不能直接使用数组的 API,因为它们是==伪数组Array-Like==++要想对这些对象使用数组的 API 就要想办法把它们转化为数组++

ES5中伪数组转换为数组

// call() 作用有两个,即调用函数并改变函数内部this指向
- 调用函数
调用 调用call()方法的这个函数,例如:fn.call()可以粗略的理解为fn()(暂时先不考虑参数的情况下)。
- 改变函数内部this指向
将函数fn内部的this指向call()方法的第一个对象参数,这么做的意义是可以使这个对象把fn这个函数(或者说方法)据为己有。

// [].slice.call(arrayLike)将经历这些步骤:让arrayLike拥有数组的slice方法,slice方法被调用,其作用目标为arrayLike,由于没有传入其他参数,slice()默认返回所有下标的元素并返回新数组
let args = [].slice.call(arguments) // collection
let imgs = [].slice.call(document.querySelectorAll('img')) // Nodelist

ES6中伪数组的转换方法

Array.from

// Array.prototype.from
let args = Array.from(arguments)
let imgs = Array.from(document.querySelectorAll('img'))

注意: ==伪数组具备两个特征,1. 按索引方式储存数据 2. 具有length属性==;如:

let arrLike = {
	0: ‘a’,
	1: ‘b’,
	2: ‘c’,
	length: 3
}

语法:Array.from(arrayLike[, mapFn[, thisArg]])

参数 含义 必选
arrayLike 想要转换成数组的伪数组对象或可迭代对象 Y
mapFn 如果指定了该参数,新数组中的每个元素会执行该回调函数 N
thisArg 可选参数,执行回调函数 mapFn 时 this 对象 N

看了这几个参数至少能看到 ==Array.from 还具备 map(遍历) 的功能==,比如我们想初始化一个长度为 5 的数组,每个数组元素默认为 1

Array.from({ length: 5 }, function () { return 1 })
// { length: 5 } 为一个伪数组(1.具备length属性,2.其储存值为空,相对于按索引方式存储)

五、生成一个新数组

ES5 生成一个新数组

let array = Array(5)
let arr = ['', '']   // 不能指定生成新数组的长度

ES6 生成一个新数组

  1. Array.from() 将伪数组转换为一个数组,也是生成一个新数组的方法
  2. Array.of() Array.of() 方法创建一个==具有可变数量参数==的新数组实例,而不考虑参数的数量或类型。
let array = Array.of(1, 2, 3, 4, 5)
console.log(array)

Array.of() 和 Array 构造函数之间的区别在于处理整数参数:Array.of(7) 创建一个具有单个元素 7 的数组,而 ==Array(7) 创建一个长度为7的空数组==(注意:这是指一个有7个空位(empty)的数组,而不是由7个undefined组成的数组)。

	Array.of(7);       // [7]
        Array.of(1, 2, 3); // [1, 2, 3]
        
        Array(7);          // [ , , , , , , ]
        Array(1, 2, 3);    // [1, 2, 3]
  1. Array.fill() 用于 快速所有元素初始化 和 替换数组的某一块区域 fill() 方法用一个固定值填充一个数组中从++起始索引到终止索引内的全部元素。不包括终止索引。++

语法:arr.fill(value[, start[, end]])

let array = [1, 2, 3, 4]
        array.fill(0, 1, 2)
        // [1,0,3,4]
// 这个操作是将 array 数组的第二个元素(索引为1)到第三个元素(索引为2)内的数填充为 0,不包括第三个元素,所以结果是 [1,0,3,4]        

六、数组中元素的查找

ES5

  1. arr.filter() 会把满足条件的元素都筛选出来,并返回一个新的数组
let array = Array.of(1, 2, 3, 4, 5)
let find = array.filter(function (item) {
  return item % 2 === 0
})
console.log(find)    // [2, 4]
  1. arr.indexOf() indexOf()方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。

ES6

  1. arr.find()
    find() 方法返回数组中满足 函数 的第一个元素的值,否则返回 undefined。 关注的是有和没有,不是关注所有的
let array = [5, 12, 8, 130, 44];
        
        let found = array.find(function(element) {
          return element > 10;
        });
        
        console.log(found);
        // 12
  1. arr.findIndex() findIndex()方法返回数组中满足提供的测试函数的==第一个元素的索引==。否则返回-1。其实这个和 find() 是成对的,不同的是它返回的是索引而不是值。

Class

Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类)。 这句话放在 ES5 可以说不为过,然而到了 ES6 这么说就已经不严谨了。因为 ES6 中已经有了专属的 class 语法了。

ES5 中如何声明一个类 (用函数的方法)

let Animal = function (type) {
          this.type = type
          this.walk = function () {
            console.log(`I am walking`)
          }
        }
        
        let dog = new Animal('dog')
        let monkey = new Animal('monkey')
        

在上述代码中,我们定义了一个叫 Animal 的类,类中声明了一个属性 type、一个方法 walk;然后通过 new Animal 这个类生成实例,完成了类的定义和实例化。 ==但是,这样写其实是不对的(把共用的东西都放到函数体里),正确的写法应该是把函数当成构造函数去用,函数里放每个实例对象都有却不同的,大家同有的东西放到原型链上==

// ES5 如何声明一个 “类”
let Animal = function (type) {
  this.type = type
}           // 每个实例对象都有一个属于自己的 type

Animal.prototype.eat = function () {
  console.log('hello i am eat')
}           // 大家同有的 eat 方法 放到原型链上

let dog = new Animal('dog')
let cat = new Animal('cat')

console.log(dog)
console.log(cat)

// 修改实例上类的方法
cat.constructor.prototype.eat = function () {
  console.log('hhhhhhh')
}

dog.eat()
cat.eat()

ES6中如何声明一个类

class Animal {
  constructor (type) {
    this.type = type
  }
  eat () {
    console.log('hello i am eat')
  }
}

let dog = new Animal('dog')
let cat = new Animal('cat')

console.log(dog)
console.log(cat)

console.log(typeof (Animal))       // function

可以发现 ==class 的类型还是 function== 可以看出在 Animal.prototype 对象上有两个方法,一个是构造函数(constructor)、一个是自定义的方法(eat)。这是不是和 ES5 的第二种写法一模一样,所以得出一个结论:==class 的方式是 function 方式的语法糖。==

二、ES6中 读写属性(set/get)

通过 get/set 来给类定一个属性

应用场景:

  1. 一个属性是个只读的
  2. 我们真的需要设置一个私有属性(闭包),然后通过一定的规则来限制对它的修改,利用 set/get就可以轻松实现。
// ES6  读写属性
let _age = 4          // 私有属性(闭包)
class Animal {
  constructor (type) {
    this.type = type
  }
  get age () {
    return _age
  }
  set age (age) {
    if (age > 4 && age < 7) {
      _age = age
    }
  }
  eat () {
    console.log('hello i am eat')
  }
}

let dog = new Animal('dog')
console.log(dog.age)
dog.age = 5
console.log(dog.age)
console.log(dog._age)

三、操作一个方法(实例对象的方法 和 静态方法)

ES5中操作一个方法

let Animal = function (type) {
  this.type = type
}

Animal.prototype.eat = function () {
  Animal.walk()
  console.log('hello i am eat')
} // 类的实例对象的方法

Animal.walk = function () {
  console.log('i am walking')
} // 类的静态方法---挂载到Animal上

let dog = new Animal('dog')
console.log(dog)
dog.eat()

ES6中操作一个方法(实例对象的方法 和 静态方法)

class Animal {
  constructor (type) {
    this.type = type
  }
  eat () {
    Animal.walk()
    console.log('i am eat')
  }
  static walk () {
    console.log('i am walking')
  }
}

let dog = new Animal('dog')
console.log(dog)
dog.eat()
Animal.walk()

注意:实例对象的方法 和 静态方法的 定义 和 使用

四、class 继承另一个类

ES5中如何继承一个类

let Animal = function (type) {
  this.type = type
}

Animal.prototype.eat = function () {
  Animal.walk()
  console.log('hello i am eat')
} // 类的实例对象的方法

Animal.walk = function () {
  console.log('i am walking')
} // 类的静态方法

let Dog = function () {
  // 第一步 初始化父类的构造函数
  // 这一步将 父类的this指向替换到子类中,将父类的构造函数里的所有东西都继承到子类里,
  // 但是父类原型链上的方法并没有继承下来,所有需要第二部进行原型链的重新指向
  Animal.call(this, 'dog')
  this.run = function () {
    console.log('i am running')
  }
}

// 第二步: 将子类的原型链指向父类
Dog.prototype = Animal.prototype

let dog = new Dog('二哈')
console.log(dog)
dog.eat()
dog.run()

ES6 如何继承一个类

class Animal {
  constructor (type) {
    this.type = type
  }
  eat () {
    Animal.walk()
    console.log('i am eat')
  }
  static walk () {
    console.log('i am walking')
  }
}
class Dog extends Animal {
  constructor (type) {
    super(type) // Useless constructor 不写constructor时,会默认会执行这些
    this.age = 3
  }
}

let dog = new Dog('哈士奇')
console.log(dog)
dog.eat()
Animal.walk()

Function Update

一、如何处理函数参数的默认值

ES5 中怎样处理函数参数的默认值

// ES5
function f (x, y, z) {
  if (y === undefined) {
    y = 7
  }
  if (z === undefined) {
    z = 42
  }
  return x + y + z
}

console.log(f(1))

ES6 中怎样处理函数参数的默认值

// ES6
function f (x, y = 7, z = 42) {
  return x + y + z
}
console.log(f(1))
console.log(f(1, undefined, 43))

函数参数是从左到右解析,如果没有默认值会被解析成 undefined,所以最好把有默认值的参数写在后面

在ES6中我们不仅可以给参数默认赋值具体的数值,==同时参数赋值支持参数的逻辑运算进行赋值==,如下段代码所示:

// ==参数可以是前面参数的 表达式==
function f (x, y = 7, z = x + y) {
          return z * 0.5
        }
        
        console.log(f(1, 7))// 4
        

扩展:

在函数体内,有时候需要==判断函数有几个参数==,一共有2个办法。在 ES5 中可以在函数体内使用 arguments 来判断。

function test (a, b = 1, c) {
          console.log(arguments.length)
	  console.log(Array.from(arguments))   ['a', 'b']
        }
        test('a', 'b')       //2

然而在 ==ES6== 中不能再使用 arguments 来判断了,但可以==借助 Function.length 来判断。==

function test (a, b = 1, c) {
          console.log(test.length)
        }
        test('a', 'b')     // 1

细心的同学发现 ==Function.length 结果和 arguments 的结果不同==! 没错,++Function.length ==是统计第一个默认参数前面的变量数:==++

function test (a = 2, b = 1, c) {
          console.log(test.length)
        }
        test('a', 'b')    // 第一个默认参数为a,其前面没有其他的变量,所以打印为0

二、如何处理不确定参数的问题

ES5中怎么处理不确定参数的问题

ES5 中利用 arguments 实现获取所有的参数

function sum () {
  let sum = 0
  // ES5 写法
  Array.prototype.forEach.call(arguments, function (item) {
    sum += item * 1
  })
  // ES6 写法,相比于ES5 可以进行伪数组到数组的转换
  // let arr = Array.from(arguments)
  // arr.forEach(function (item, index) {
  //   sum += item * 1
  // })
  return sum
}
console.log(sum(1, 23, 45, 88))

ES6中怎么处理不确定参数的问题

function sum (...nums) {
  // Rest parameter
  console.log(nums)
  let sum = 0
  nums.forEach(function (item) {
    sum += item
  })
  return sum
}
console.log(sum(12, 22, 3))

==当然,Rest Parameter 也可以和其他参数一起来用,比如:==

function sum (base, ...nums) {
          let num = base
          nums.forEach(function (item) {
            num += item * 1
          })
          return num
}        
        console.log(sum(30, 1, 2, 3))  // 36
        console.log(sum(30, 1, 2, 3, 4))   // 40

==arguments 不是数组==,所以不能直接使用数组的原生 API 如 forEach,而 ==Rest Parameter 是数组==,可以直接使用数组的原生 API。

三、Rest 与 speard (二者相反,rest会把参数装到一个数组, speard会把数组中数据打散,传到函数中)

function sum (x = 1, y = 3, z = 9) {
  return x + y + z
}

let data = [4, 5, 6] // 后端返回的数据
console.log(sum(data[0], data[1], data[2])) // 憨憨做法
console.log(sum.apply(this, data)) // ES5做法
// 调用apply方法的时候,第一个参数是对象(this), 第二个参数是一个数组集合,将这个数组集合赋值给 arguments
console.log(sum(...data))  // ES6做法 speard

==apply 与 call 的区别:==

image.png

四、ES6中的 箭头函数

  1. 箭头后面是个表达式,表达式的值就作为这个函数的返回值、
  2. 返回值是字面量对象 如果返回值是字面量对象,一定要用小括号包起来
let person = (name) => ({
          age: 20,
          addr: 'Beijing City'
})
  1. 其他情况就要中规中矩的写好
() => {
  return {
    .......
  }
}

==关于 this 的拓展==

  1. 总结: ==作为事件处理函数==,es5的this指向触发当前事件的对象元素,es6箭头函数指向window
  2. ==在对象方法中的this== ES5中谁在调用这个函数,this就指向谁 箭头函数中 this 的指向是 在写这个代码的时候this的指向(即箭头函数中对 this 的处理是定义时) es6函数体内的this对象是【使用时】所在的对象,而不是【定义时】所在的对象 es6函数体内的this对象是【定义时】所在的对象,而不是【使用时】所在的对象
  3. 在window中定义的方法,es5和es6的this都一样指向window

注意: 默认指向定义它时,所处上下文的对象的this指向。即ES6箭头函数里this的指向就是上下文里对象this指向,偶尔没有上下文对象,this就指向window 上下文:即是找上下文的函数对象,就默认为window


Object

一、关于对象属性简写 的问题

1.在 ES6 之前 Object 的属性必须是 key-value 形式,如下:

var x = 0, y = 0;
        obj = { x: x, y: y };

在 ES6 之后是可以用简写的形式来表达:

var x = 0, y = 0
        obj = { x, y }

2.在 ES6 之前 Object 的 ++key 在定义时必须是字符串++,如果想增加“动态”的 key,必须是先计算出 key,利用 ==object[key] = value== 的方式来修改; 但是:在 ES6 之后可以直接用变量或者表达式来定义 key。

let x = 1      let y = 2     let z = 5
let obj = {
  x,
  [z + y]: 12           // ES6
}

obj.[z + y] = 12        // ES5

3.Method Properties 从 ES6 开始对象内的方法可以简写,包括常规函数和异步函数。

let x = 1
let y = 2
let z = 5
let obj = {
  x,
  [z + y]: 12,
  hello: function () {       // ES5 只支持常规的函数
    console.log('hello')
  },                     
  hello () {                 // ES6 支持常规的函数和异步函数。
    console.log('hello')    
  },
  * hello () {               // ES6 支持常规的函数和异步函数。
    console.log('i am 异步函数')   
  }
}
console.log(obj)

二、Set 和 Map

在 JavaScript 里通常使用 Array 或 Object 来存储数据。但是在频繁操作数据的过程中查找或者统计并需要手动来实现,并不能简单的直接使用。 比如如何保证 Array 是去重的,如何统计 Object 的数据总数等,必须自己去手动实现类似的需求,不是很方便。 在 ES6 中为了解决上述痛点,新增了数据结构 Set 和 Map,它们分别对应传统数据结构的“集合”和“字典”。

Set 数据结构

1.生成 Set 实例

let s = new Set()

可以定义一个空的 Set 实例,也可以在实例化的同时传入默认的数据。

let s = new Set([1, 2, 3, 4])

==初始化的参数必须是可遍历的==,可以是数组,对象 或者 自定义遍历的数据结构。 2.添加数据

s.add('hello')
s.add('goodbye')    
或者
s.add('hello').add('goodbye')      

[!NOTE] 敲黑板了,==Set 数据结构不允许数据重复==,所以添加重复的数据是无效的

3.删除数据 删除数据分两种,一种是删除指定的数据,一种是删除全部数据。

	// 删除指定数据
        s.delete('hello') // true
        // 删除全部数据
        s.clear()

4.统计数据(has() size) Set 可以快速进行统计数据,如==数据是否存在、数据的总数。==

// 判断是否包含数据项,返回 true 或 false
        s.has('hello') // true
        // 计算数据项总数(size 是一个属性,不是方法)
        s.size // 2

5.查询数据

  • keys():返回 键名 的遍历器
  • values():返回 键值 的遍历器
  • entries():返回 键值对 的遍历器
  • forEach():使用回调函数遍历每个成员
  • for…of:可以直接遍历每个成员
console.log(s.keys()); // SetIterator {"hello", "goodbye"}
console.log(s.values()); // SetIterator {"hello", "goodbye"}
console.log(s.entries()); // SetIterator {"hello" => "hello", "goodbye" => "goodbye"}

s.forEach(item => {
  console.log(item)
})

for (const item of s) {
  console.log(item)
}

6.修改数据 要先删除 再add

Map 数据结构(==Map 里的 key 可以是任意值==)

Map 是用来实现字典的功能(Object 键值对) 一个Object的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值,包括函数、对象、基本类型。

1.实例化

let map = new Map([iterable])
let map = new Map([[1, 2], [2, 3], [3, 6]])

==Iterable 可以是一个 ++数组++ 或者 ++其他 iterable 对象(可遍历对象)++==,其元素为键值对(两个元素的数组,例如: [[ 1, ‘one’ ],[ 2, ‘two’ ]])。 每个键值对都会添加到新的 Map。null 会被当做 undefined。

2.添加数据(==Map 里的 key 可以是任意值==)

	let keyObj = {}
        let keyFunc = function () {}
        let keyString = 'a string'
        
        // 添加键
        map.set(keyString, "和键'a string'关联的值")
        map.set(keyObj, '和键keyObj关联的值123')
        map.set(keyFunc, '和键keyFunc关联的值')

3.删除数据

// 删除指定的数据
map.delete(keyObj)
// 删除所有数据
map.clear()

4.统计数据

console.log(map.size) // 统计所有 key-value 的总数
// 判断是否有 key-value
console.log(map.has(keyObj)) // true

5.查询数据

  • get() 方法返回某个 Map 对象中的一个指定元素
  • keys() 返回一个新的 Iterator 对象。它包含按照顺序插入 Map 对象中每个元素的 key 值
  • values() 方法返回一个新的 Iterator 对象。它包含按顺序插入Map对象中每个元素的 value 值
  • entries() 方法返回一个新的包含 [key, value] 对的 Iterator 对象,返回的迭代器的迭代顺序与 Map 对象的插入顺序相同
  • forEach() 方法将会以插入顺序对 Map 对象中的每一个键值对执行一次参数中提供的回调函数
  • for…of 可以直接遍历每个成员
console.log(map.get(keyObj)) // 和键keyObj关联的值123
 for ([key, value] of map) {
    console.log(key, value)
 }

三、对象的复制

ES5中对象的kaob

let target = {}
let source = {a: 4, b: 'hhh'}
console.log('source:', source)

for (const key in source) {
  target[key] = source[key]
}
console.log('target:', target)

ES6中对象如何进行拷贝

基本语法 Object.assign(target, …sources)

从语法上可以看出源对象的个数是不限制的(零个或多个),如果是零个直接返回目的对象,如果是多个相同属性的会被后边的源对象的属相覆盖。

let target2 = {}
Object.assign(target2, source)
console.log(target2)

// source 为多个对象
const first = {
  name: 'Bob'
}
const last = {
  lastName: 'Smith'
}

let person = Object.assign(first, last)
console.log(person)/*{ name: 'Bob', lastName: 'Smith' }*/

Template

  1. 字符串很长要换行 对于多行的字符串,之前是这样处理 ES5
console.log('string text line1\n' + 'string text line2')
        // "string text line 1
        // string text line 2"

现在可以这样做了 ES6

console.log(`string text line1
string text line2`)
// "string text line 1
// string text line 2"

完全不需要 \n 来参与。 2.字符串中有变量或者表达式 ES5 字符串拼接:

var a = 20
var b = 10
var c = 'JavaScript'
var str = 'My age is ' + (a + b) + ' and I love ' + c
console.log(str)

ES6 模板字符串

let str = `your age is ${a + b} i love ${c}`
console.log(str)

在这里你可以任意==插入变量或者表达式==,只要用 ${} 包起来就好。

3.字符串中有逻辑运算 ES5 使用 字符串拼接+逻辑判断

var retailPrice = 20
var wholesalePrice = 16
var type = 'retail'
        
var showTxt = ''        
if (type === 'retail') {
    showTxt += '您此次的购买单价是:' + retailPrice
} else {
    showTxt += '您此次的批发价是:' + wholesalePrice
}

ES6 用函数充当 ==模板引擎 Tag 函数==

你输入一个字符串 这个字符串会和一个函数作关联, 这个函数就是用来处理你 字符串逻辑的

function Price (strings, type) {
  let str = strings[0]
  const retailPrice = 20
  const wholesalePrice = 10
  let showText
  if (type === 'retail') {
    showText = '购买单价是:' + retailPrice
  } else {
    showText = '购买批发价是:' + wholesalePrice
  }
  return `${str}${showText}`
}

let showText = Price`您此次的${'retail'}`  // `您此次的${'retail'}` 为参数
					  // 函数 return 的结果为最终的结果
console.log(showText)

strings 参数指的是 Tag 函数后面==被变量分割开的字符串集合==,type 参数是对应第一个变量,Tag 函数可以有==多个 type 类似的参数==


解构赋值

一、Array 的解构赋值

1.简单的数组赋值

let [firstName, surname] = ['Ilya', 'Kantor']
        console.log(firstName) // Ilya
        console.log(surname) // Kantor
let arr = ['hello', 'wrold']
let [firstName] = arr // 解构复制
console.log(firstName)   // hello

2.跳过不关心的元素 进行赋值

如果想忽略数组的某个元素对变量进行赋值,可以使用逗号来处理。

// second element is not needed
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
console.log( title );     // Consul

3.==赋值元素可以是任意可遍历的对象== 赋值的元素不仅是数组,它可以是任意可遍历的对象

let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);

4.不仅可以 ==给简单的变量赋值==, 还可以 ==给对象的属性赋值==

注意: 给对象的属性赋值时, 由于对象已经存在,赋值时 不能加 let

let users = {};
[users.name, users.surname] = 'Ilya Kantor'.split(' ');

console.log(users.name) // Ilya

5.如何在循环体中做 变量赋值([key, value])

解构赋值在循环体中的应用,可以配合 entries 使用(改成键值对得形式)。

let user = {
  name: 's',
  surname: 'a'
}
for (const [key, value] of Object.entries(user)) {
  console.log(key, value)
}

6.数组解构赋值如何处理 rest参数得问题

let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
        
        console.log(name1); // Julius
        console.log(name2); // Caesar
        
        // Note that type of `rest` is Array.
        console.log(rest[0]); // Consul
        console.log(rest[1]); // of the Roman Republic
        console.log(rest.length); // 2

注意:我们可以使用 rest 来接受赋值数组的剩余元素,不过要确保这个 rest 参数是放在被赋值变量的最后一个位置上。

7.解构赋值 可以设置默认值 如果数组的内容少于变量的个数,并不会报错,没有分配到内容的变量会是 undefined。

    let [firstName, surname] = [];
    console.log(firstName); // undefined
    console.log(surname); // undefined

当然你也可以给变量赋予默认值,防止 undefined 的情况出现:

    let [name = "Guest", surname = "Anonymous"] = ["Julius"];
    console.log(name);    // Julius (from array)
    console.log(surname); // Anonymous (default used)

二、Object 得解构赋值

1.基本用法

let {var1, var2} = {var1:…, var2…}

2.指定默认值

let options = {
          title: "Menu"
        };

let {width = 100, height = 200, title} = options;
        
        alert(title);  // Menu
        alert(width);  // 100
        alert(height); // 200
        

3.对象解构赋值如何处理 ++rest 运算符++

let options = {
          title: "Menu",
          height: 200,
          width: 100
        };
        
let {title, ...rest} = options;
        
// now title="Menu", rest={height: 200, width: 100}
alert(rest.height);  // 200
alert(rest.width);   // 100      

4.==嵌套对象== 的使用

如果一个 Array 或者 Object 比较复杂,它嵌套了 Array 或者 Object,那只要被赋值的结构和右侧赋值的元素的结构一致就好了 (左右结构一致

let options = {
          size: {
            width: 100,
            height: 200
          },
          items: ["Cake", "Donut"],
          extra: true    // something extra that we will not destruct
        };
        
        // destructuring assignment on multiple lines for clarity
        let {
          size: { // put size here
            width,
            height
          },
          items: [item1, item2], // assign items here
          title = "Menu" // not present in the object (default value is used)
        } = options;
        
        alert(title);  // Menu
        alert(width);  // 100
        alert(height); // 200
        alert(item1);  // Cake
        alert(item2);  // Donut
        

Promise

一、ES5 中异步操作 与 回调的关系

// 异步 读取文件
const fs = require('fs');
const path = require('path');

// callback 方式 获取一个文件的内容
function getFileContent(fileName, callback) {
    const fullFileName = path.resolve(__dirname, './files', fileName);
    fs.readFile(fullFileName, (err, data) => {
        if (err) {
            console.error(err);
            return
        }
        callback(
            JSON.parse(data.toString())  // 将字符串转换为 json对象的形式
                                         // JSON.parse(data.toString()) === aData
        )
    })
}

// 测试
getFileContent('a.json', aData => {
    console.log('a.data', aData);
    getFileContent(aData.next, bData => {
        console.log('b.data', bData);
        getFileContent(bData.next, cData => {
            console.log('c.data', cData)
        })
    })
})

二、ES6 中 Promise的使用

Promise 就是为了解决“回调地狱”问题的,它可以将异步操作的处理变得很优雅。

1.基本语法

new Promise( function(resolve, reject) {…} );
  • new Promise(fn) 返回一个Promise 对象
  • 在fn 中指定异步等处理
  • 处理结果正常的话,调用resolve(处理结果值)
  • 处理结果错误的话,调用reject(Error对象)

2.状态转换

在这里必须说明下 ==Promise 内部是有状态的(pending、fulfilled、rejected)==,Promise 对象根据状态来确定执行哪个方法。Promise 在实例化的时候状态是默认 pending 的,当异步操作是完成的,状态会被修改为 fulfilled,如果异步操作遇到异常,状态会被修改为 rejected

状态转化是单向的,不可逆转,已经确定的状态(fulfilled/rejected)无法转回初始状态(pending),而且只能是从 pending 到 fulfilled 或者 rejected

==3.Promise.prototype.then()== 仔细阅读最后的 代码

基本语法: promise.then(onFulfilled, onRejected);

promise.then(onFulfilled, onRejected) onFulfilled对应 resolve方法 onRejected对应 reject方法 onFulfilled, onRejected 这两个参数 都是函数类型 如果你传入的参数 是非函数,那么.then()将会返回一个 空的 promise 对象

var promise = new Promise(function (resolve, reject) {
          resolve('传递给then的值')
        })
        promise.then(function (value) {      // resolve 的 参数传给 value
          console.log(value)
        }, function (error) {                // reject 的参数传给 error
          console.error(error)
        })
        

这段代码创建一个 Promise 对象,定义了处理 onFulfilled 和 onRejected 的函数 这个 Promise 对象会在变为 resolve 或者 reject 的时候分别调用相应注册的回调函数。

  • 返回一个正常值的时候,这个值会传递给 Promise 对象的 onFulfilled 方法。
  • 产生异常的时候,这个值则会传递给 Promise 对象的 onRejected 方法。

==4.Promise.resolve()==

一般情况下我们都会使用 new Promise() 来创建 Promise 对象,但是除此之外我们也可以使用其他方法。

在这里,我们将会学习如何使用 Promise.resolve 和 Promise.reject 这两个方法。

==静态方法 Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式==

new Promise(function (resolve) {
          resolve(42)
        })
// 等同于
Promise.resolve(42)

5.Promise.prototype.catch()

使用 Promise 对象的 catch 方法来捕获异步操作过程中出现的任何异常。 ==可以省去每一个 .then() 中的 onRejected 函数参数,将对每一个.then()语句中的错误统一处理== throw Error 和 reject 都可以触发 catch 的捕获,但是不建议在 Promise 内部使用 throw 来触发异常,而是使用 reject(new Error()) 的方式来做,因为 ==throw 的方式并没有改变 Pronise 的状态==

==6.Promise.all() 应用于处理 同时需要返回很多接口的数据==

基本语法

Promise.all(promiseArray);
	var p1 = Promise.resolve(1)
        var p2 = Promise.resolve(2)
        var p3 = Promise.resolve(3)
        Promise.all([p1, p2, p3]).then(function (results) {
          console.log(results) // [1, 2, 3]
        })
        
  • Promise.all 生成并返回一个新的 Promise 对象,所以它可以使用 Promise 实例的所有方法,参数传递promise数组中所有的 Promise 对象都变为resolve的时候,该方法才会返回 resolve, 新创建的 Promise 则会使用这些 promise 的值。

  • 如果参数中的任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的 Promise 对象。

  • 由于参数数组中的每个元素都是由 Promise.resolve 包装(wrap)的,所以Paomise.all 可以处理不同类型的 promose对象。

==7.Promise.race() 用于处理 主备用资源 谁先返回使用谁==

基本语法

Promise.race(promiseArray)

示例:

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 1000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(2)
  }, 10)
})

Promise.race([p1, p2]).then((value) => {
  console.log(value)
})

使用Promise 对 上面ES5回调地狱 的改写

const fs = require('fs');
const path = require('path');

// 用 promise 获取文件 内容
function getFileContent(fileName) {
  // new 一个Promise时  此时Promise的状态是 padding 结果是 undefined
  return new Promise((resolve, reject) => {
    const fullFileName = path.resolve(__dirname, 'files', fileName);
    fs.readFile(fullFileName, (err, data) => {
      if (err) {
        reject(err);  // reject 被执行,Promise的状态变为 reject 结果变为 error
        return
      }
      // resolve 被执行,Promise的状态变为 fulfilled 结果变 JSON.parse(data.toString())
      resolve(
        JSON.parse(data.toString())
      )
    })
  })
}

// 写法一:
// getFileContent('a.json')
//     .then(getFileContent('b.json'))
//     .then(getFileContent('c.json'))

// .then() 和 .catch() 方法是 promise 原型上的方法(即:实例方法)
/* promise.then(onFulfilled, onRejected)  onFulfilled对应 resolve方法    onRejected对应 reject方法
 *              onFulfilled, onRejected 这两个参数 都是函数类型
 *              但是 getFileContent('b.json') 不是函数,是函数执行后的 返回值
 *              所以 按理说 应该传入两个函数,可是 传入一个 promise对象
 *              原因是 :规定两个函数参数如果不传,将会执行传入的表达式,相当于执行了
*/
// 如果你传入的参数 是非函数,那么.then()将会返回一个 空的 promise 对象

// 写法二:
getFileContent('a.json')
    .then(() => {
      return getFileContent('b.json');   // 不加 return 返回的仍是一个空的 promise 对象,这样会导致对下一个.then() 不会造成影响
    }, (err) => {
      console.log(err);
    })
    .then(() => {
      return getFileContent('c.json');
    }, (err) => {
      console.log(err);
    });


// 写法三:
getFileContent('a.json')
    .then(() => {
      return getFileContent('b.json');   // 不加 return 返回的仍是一个空的 promise 对象,这样会导致对下一个.then() 不会造成影响
    })
    .then(() => {
      return getFileContent('c.json');
    })
    .catch(err => {
      console.log(err);
    })

/* 
 * 在这三个文件中都有下一个文件的名字
 * 可以通过 return getFileContent('b.json'); 返回的 resolve 值中获取下一个文件名
 * 在下一个 then 方法中做参数使用
 */