ES6学习笔记——持续更新

本篇整理了一下ES6的一些常用基础知识,关于ES6的知识将会继续学习和补充。
如有记录得不对的地方,欢迎探讨。

目录

  • ES6规范
    • 为什么使用ES6?
    • let关键字
    • const关键字
      • var、let、const 对比:
    • 对象字面量的增强写法
      • 属性的增强写法
      • 函数的增强写法
    • 解构赋值
      • 数组解构
      • 对象解构
    • 箭头函数
      • 箭头函数中的this
      • 箭头函数经典面试题
    • 剩余参数
    • ES6内置对象扩展
      • 数组的扩展方法
        • ...扩展运算符
        • 构造函数方法:Array.from()
        • 实例方法 find()
        • 实例方法 findIndex()
        • 实例方法 includes()
      • string 的扩展方法
        • 模板字符串``
        • startsWith() 和 endsWith()
        • repeat()
    • Set 数据结构
      • 实例化
      • 实例方法
      • Set 遍历
    • Promise
      • 传统异步处理的弊端
      • Promise 的基本使用
      • Promise 的三种状态
      • Promise 的链式调用
      • Promise 中 all 的用法

ES6规范

ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。

ES6 实际上是一个泛指,泛指 ES2015 及后续的版本。

为什么使用ES6?

每一次标准的诞生都意味着语言的完善,功能的加强。JavaScript语言本身也有一些令人不满意的地方。

  • 变量提升特性增加了程序运行时的不可预测性
  • 语法过于松散,实现相同的功能,不同的人可能会写出不同的代码

let关键字

  • ES6中新增的用于声明变量的关键字

  • let声明的变量只在所处于的块级有效

  • 使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性

if (true){
	let a = 10;
}
console.log(a); //ReferenceError: a is not defined
  • 特点:

    • 不存在变量提升

      a = 1
      console.log(a)//ReferenceError: Cannot access 'a' before initialization
      let a
      
    • 暂时性死区

      var tmp = 123;
      if(true){
      	tmp = 'abc';
      	let tmp;
      } //ReferenceError: Cannot access 'tmp' before initialization
      
  • 经典面试题:

    Q1:请问下面代码输出结果是什么?

     var arr = [];
     for (var i = 0; i < 2; i++) {
         arr[i] = function () {
             console.log(i); 
         }
     }
    
     arr[0](); //2
     arr[1](); //2
    

此题的关键点在于变量i是全局的,函数执行时输出的都是全局作用域下的i值。

**Q2 :请问下面代码输出结果是什么?**
 let arr = [];
 for (let i = 0; i < 2; i++) {
     arr[i] = function () {
         console.log(i); 
     }
 }
 arr[0](); //0
 arr[1](); //1

此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值.

const关键字

  • 作用:声明常量,常量就是值(内存地址)不能变化的量。

  • 特点:

    • 具有块级作用域

      if(true){
      	const a = 10;
      }
      console.log(a); //ReferenceError: a is not defined
      
    • 声明常量时必须赋值

      const PI; //SyntaxError: Missing initializer in const declaration
      
    • 常量赋值后,值(内存地址)不能修改

      const PI = 3.14;
      PI = 10; //Assignment to constant variable.
      

      这里所说的值不能修改,其实指的是内存地址不能修改。我们来看下面这个数组的例子:

      const arr = [100,200];
      arr[0] = 1;
      arr[1] = 2;
      console.log(arr); //[1,2]
      arr = [3,4]; //Assignment to constant variable.
      

      可见,我们可以修改数组里面的数值,但是不能修改数组的内存地址。

      其实,在ES6中,常量的含义是指向的对象不能修改,但是可以改变对象内部的属性。

var、let、const 对比:

var let const
函数级作用域 块级作用域 块级作用域
变量提升 不存在变量提升 不存在变量提升
值可更改 值可更改 值不可更改

对象字面量的增强写法

属性的增强写法

  • ES5中:

    const name = "Kobe";
    const age = "18";
    const obj = {
    	name: name,
    	age: age
    }
    
  • ES6中字面量增强写法:

    const name = "Kobe";
    const age = "18";
    const obj = {
    	name,
    	age
    }
    

函数的增强写法

  • ES5中:

    const obj = {
    	run: function () {
    	
    	},
    	eat: function () {
    	
    	}
    }
    
  • ES6中:

    const obj = {
    	run () {
    	
    	},
    	eat () {
    	
    	}
    }
    

解构赋值

按照一定模式,从数组中或对象中提取值,将提取出来的值赋值给另外的变量。

数组解构

let [a,b,c] = [1,2,3];
console.log(a); //1
console.log(b); //2
console.log(c); //3

如果解构不成功,变量的值为undefined:

let [a] = [];
console.log(a); //undefined
let [b,c] = [1];
console.log(b); //1
console.log(c); //undefined

对象解构

 let person = { name: 'zhangsan', age: 20 }; 
 let { name, age } = person;
 console.log(name); // 'zhangsan' 
 console.log(age); // 20
let {name: myName, age: myAge} = person; // myName myAge 属于别名
 console.log(myName); // 'zhangsan' 
 console.log(myAge); // 20

箭头函数

ES6 中新增函数定义方式:箭头函数

() => {} 
const fn = () => {}

其中,fn 是函数名,小括号内是形参,大括号内是函数体。

  • 函数体中只有一句代码且代码的执行结果就是返回值,可以省略大括号

    如下面两种写法都可以表示一个返回加法结果的函数:

    function sum(num1, num2) { 
         return num1 + num2; 
     }
     const sum = (num1, num2) => num1 + num2; 
    
  • 如果形参只有一个,可以省略小括号

    例如:

     function fn (v) {
         return v;
     } 
     const fn = v => v;
    

箭头函数中的this

箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this。即向外层作用域中, 一层层查找this, 直到有this的定义.

如:

function fn (){
	console.log(this);
	return () => {
		console.log(this);
	}
}
const obj = { name:'zhangsan' };
const refn = fn.call(obj); //obj
refn(); //obj

箭头函数经典面试题

var age = 100;

var obj = {
	age: 20,
	say: () => {
        console.log(this)//window
		alert(this.age)//100
	}
}

obj.say();

上面这段代码的输出结果是100。

我们说箭头函数不绑定this,因此在调用 say 属性中的箭头函数时如果不指定this,那么this指向window。而window中的age是100。

const obj = {
  aaa() {
      console.log(this)//obj
    setTimeout(function () {
      setTimeout(function () {
        console.log(this); // window
      })

      setTimeout(() => {
        console.log(this); // window
      })
    })

    setTimeout(() => {
      setTimeout(function () {
        console.log(this); // window
      })

      setTimeout(() => {
        console.log(this); // obj
      })
    })
  }
}

obj.aaa()

剩余参数

剩余参数语法允许我们将一个不定数量的参数表示为一个数组。

function fn(a,...b){
	console.log(a); //1
	console.log(b); //[2,3,4]
}
fn(1,2,3,4);

剩余参数可以和解构配合使用:

let students = ['张三','李四','王五'];
let [a,...b] = students;
console.log(a); //张三
console.log(b); //["李四", "王五"]

ES6内置对象扩展

数组的扩展方法

…扩展运算符

扩展运算符可以将数组或者对象转为用逗号分隔的参数序列

let arr = [1,2,3];
console.log(...arr); // 1 2 3
  • 应用1:数组拼接

let arr1 = [1,2,3];
let arr2 = [4,5,6];
let arr3 = [...arr1, ...arr2];
console.log(arr3); //[1,2,3,4,5,6]
  • 应用2:将类数组转化为真正的数组

    let oDivs = document.getElementsByTagName('div'); 
    oDivs = [...oDivs];
    

构造函数方法:Array.from()

  • 将类数组或可遍历对象转换为真正的数组

    let arrLike = {
    	'0': 'a',
    	'1': 'b',
    	'2': 'c',
    	length: 3
    }
    let arr = Array.from(arrLike);
    console.log(arr); //['a','b','c']
    
  • 方法还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组

    let arrLike = {
    	'0': '1',
    	'1': '2',
    	'2': '3',
    	length: 3
    }
    
    let arr = Array.from(arrLike, item => item*2 )
    console.log(arr); //[2,4,6]
    

实例方法 find()

作用:用于找出第一个符合条件的数组成员,如果没有找到返回undefined

let arr = [{
	id: 1,
	name: 'zhangsan'
},{
	id: 2,
	name: 'lisi'
}];
let target = arr.find( (item,index) => item.id == 2);
console.log(target.name); //lisi

实例方法 findIndex()

用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1

let arr = [1,10,30,40];
let index = arr.findIndex( value => value > 20);
console.log(index); //2

实例方法 includes()

表示某个数组是否包含给定的值,返回布尔值。

[1, 2, 3].includes(2) // true 
[1, 2, 3].includes(4) // false

string 的扩展方法

模板字符串``

ES6新增的创建字符串的方式,使用反引号定义。

let name = `zhangsan`;
  1. 模板字符串中可以解析变量
let name = '路飞';
let sayLove = `我喜欢${name}`;
console.log(sayLove); //我喜欢李琳琦
  1. 模板字符串中可以换行
let obj = {
	name: '张三',
	age: 20,
	sex: '男'
}
let html = `
${obj.name} ${obj.age} ${obj.sex}
`
; console.log(html); /*
张三 20
*/
  1. 在模板字符串中可以调用函数
let sayHaha = () => '哈哈哈哈';
let result = `我太帅了${sayHaha()}`;
console.log(result); //我太帅了哈哈哈哈

startsWith() 和 endsWith()

  • startsWith():表示参数字符串是否在原字符串的头部,返回布尔值

  • endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值

 let str = 'Hello world!';
 str.startsWith('Hello') // true 
 str.endsWith('!')       // true

repeat()

repeat方法表示将原字符串重复n次,返回一个新字符串。

let name = '路飞';
console.log(name.repeat(3)); //李琳琦李琳琦李琳琦

Set 数据结构

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

实例化

  • Set本身是一个构造函数,用来生成 Set 数据结构

    const s = new Set();
    
  • Set函数可以接受一个数组作为参数,用来初始化

    const set = new Set([1, 2, 3, 4, 4]);
    console.log(set); //[1,2,3,4]
    

实例方法

  • add(value):添加某个值,返回 Set 结构本身
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功
  • has(value):返回一个布尔值,表示该值是否为 Set 的成员
  • clear():清除所有成员,没有返回值
const s = new Set([1,2,3,4]);
s.add(3).add(4).add(5);
console.log(s); //Set(5) {1, 2, 3, 4, 5}
console.log(s.delete(4)); //true
console.log(s.has(4)); //false
s.clear();
console.log(s); //Set(0) {}

Set 遍历

Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值

s.forEach(value => console.log(value))

Promise

传统异步处理的弊端

Promise 是异步编程的一种解决方案

什么时候我们会来处理异步事件呢?

一种很常见的场景应该就是网络请求了。

假如我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能像简单的3+4=7一样将结果返回。所以往往我们会传入另外一个函数,在数据请求成功时,将数据通过传入的函数回调出去。

如果只是一个简单的网络请求,那么这种方案不会给我们带来很大的麻烦。但是,当网络请求非常复杂时,就会出现回调地狱。比如如下的一个场景(比较夸张的例子):

$.ajax('url1', function(data1) {
  $.ajax(data1['url2'], function(data2) {
    $.ajax(data2['url3'], function(data3) {
      $.ajax(data3['url4'], function(data4) {
        console.log(data4);
      })
    })
  })
})

在上面的例子中,

  • 我们需要通过一个url1从服务器加载一个数据data1,data1中包含了下一个请求的url2

  • 我们需要通过data1取出url2,从服务器加载数据data2,data2中包含了下一个请求的url3

  • 我们需要通过data2取出url3,从服务器加载数据data3,data3中包含了下一个请求的url4

  • 发送网络请求url4,获取最终的数据data4

这样的代码难看而且不容易维护,我们更加期望的是一种更加优雅的方式来进行这种异步操作。Promise 就可以以一种非常优雅的方式来解决这个问题。

Promise 的基本使用

我们用一个定时器来模拟异步事件,假设下面的 data 是从网络上2秒后请求的数据,console.log 就是我们的处理方式:

setTimeout(() => {
  let data = 'hahaha'
  console.log(data);
},2000)

这是我们过去的处理方式,我们将它换成 Promise 代码:

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
    reject('error')
  },2000)
}).then(data => {
  console.log(data);
}).catch(data => {
  console.log(data);
})

这个例子会让我们感觉脱裤放屁,多此一举,但是在之后的学习和运用中会发现这种链式编程是很实用的。

  • Promise 是类,需要用 new 来实例化

  • Promise 的参数是一个函数(一般用箭头函数书写),在这个函数中一般有两个参数,resolve/reject

  • resolve 和 reject 也是函数,通常情况下,我们会根据请求数据的成功和失败来决定调用哪一个:

    • 如果是成功的,那么通常我们会调用resolve(success),这个时候,我们后续的then会被回调。

    • 如果是失败的,那么通常我们会调用reject(error),这个时候,我们后续的catch会被回调。

这就是 Promise 的基本使用了。

Promise 的三种状态

当我们开发中有异步操作时, 就可以给异步操作包装一个Promise

异步操作之后会有三种状态:

  1. pending等待状态,比如正在进行网络请求,或者定时器没有到时间。

  2. fulfilled满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()

  3. rejected拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QLevzyIV-1572850721130)(D:\52007前端学习\前端笔记\images\javascript\Promise的三种状态.png)]

Promise 的链式调用

我们在看Promise的流程图时,发现无论是then还是catch都可以返回一个Promise对象。所以,我们的代码其实是可以进行链式调用的:

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('li')
  },1000)
}).then(data => {
  console.log(data)//li
  return new Promise((resolve, reject) => {
    resolve(data + 'lin')
  })
}).then(data => {
  console.log(data);//lilin
  return Promise.resolve(data + 'qi')
}).then(data => {
  console.log(data)//lilinqi
  return Promise.reject(data + 'dashabi')
}).then(data => {
  console.log(data)//不会有打印
  return Promise.resolve(data + 'dashazhu')
}).catch(data => {
  console.log(data)//lilinqidashabi
  return Promise.resolve(data + '傻逼')
}).then(data => {
  console.log(data)//lilinqidashabi傻逼
})

在上面的第12行中,我们直接通过Promise包装了一下新的数据,将Promise对象返回了

  • Promise.resovle():将数据包装成Promise对象,并且在内部回调resolve()函数

  • Promise.reject():将数据包装成Promise对象,并且在内部回调reject()函数

除此之外,链式调用中如果我们希望数据直接包装成Promise.resolve,那么在return时我们可以直接返回数据:

then(data => {
  //return Promise.resolve(data + 'dashazhu')
  return data + 'dashazhu'
}

如果数据直接包装成Promise.reject,那么在throw中可以直接返回数据:

catch(data => {
  //return Promise.reject(data + 'dashazhu')
  throw data + 'dashazhu'
})

结果依然是一样的。

Promise 中 all 的用法

我们来假设一种场景,当有两个网络请求同时发送请求时,我们不知道哪一个先返回了数据,一般来说我们需要在各自的函数中定义一个变量 isTrue1 和 isTrue2,然后用if判断当两个变量都为true时执行一些代码:

let isResult1 = false
let isResult2 = false
$ajax({
  url: '',
  success: function () {
    console.log('结果1');
    isResult1 = true
    handleResult()
  }
})
// 请求二:
$ajax({
  url: '',
  success: function () {
    console.log('结果2');
    isResult2 = true
    handleResult()
  }
})

function handleResult() {
  if (isResult1 && isResult2) {
    //
  }
}

但是这样不仅麻烦,代码也不够优雅。

Promise 中提供了一个 all 属性,优雅地解决了这个问题:

Promise.all([
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('True1')
    }, 1000)
  }),
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('True2')
    }, 2000);
  })
]).then(results => {
  console.log(results)//["True1", "True2"]
  console.log(results[0])//True1
  console.log(results[1])//True2
})

Promise.all() 中需要传入参数,参数是一个可以迭代的对象,比如数组。

在数组中我们可以 new 若干个Promise,并统一用then处理,此时then拿到的数据是一个数组,数组下标对应着all参数数组中的Promise对象。

你可能感兴趣的:(ES6学习笔记——持续更新)