let a
let a
//报错:Uncaught SyntaxError: Identifier 'a' has already been declared
var
console.log(a) //undefined
var a=1
console.log(a) //1
var在块级作用域中,会将var的声明提前至前面,源代码中,其实真实的代码应该是
var a
console.log(a) //undefined
a=1
console.log(a) //1
let
console.log(a)
//报错:Uncaught ReferenceError: Cannot access 'a' before initialization
let a=1
console.log(a)
let命令声明的变量只在其块级作用域中有效,就是{}中,在块级作用域内,若存在用let命令声明的变量,则所在区块对该变量形成封闭作用域,即该变量无视外部的同名变量。
let a=2
{
let a=1
console.log(a) //1
}
还有一个网上常说的例子
let items = document.querySelectorAll('.item');
for (var i = 0; i < items.length; i++) {
items[i].onclick = function () {
items[i].style.background = 'blue';
}
}
当我们点击第一,第二,或者第三个块时,类型错误,就是没有找到 item[i] 这个变量,这是由于使用 var 定义的变量。
在for循环中这个块中使用var声明的变量,默认就是全局变量,在 window 上,可以直接访问到。而在for循环这个块中访问到的是全局的变量 i , i++ 在执行完之后,此时在 window.i = 3
这时就用到了,let 声明变量,就会在for循环这个块中,产生一个块级作用域,与全局作用域无关,items[i] 就是我们想要的变量。
1、const 声明一个只读的常量。一旦声明,常量的值就不能改变。正是因为其值不能进行修改,因此, ,const一旦声明变量,就必须立即初始化,不能留到以后赋值,const定义的变量潜规则要大写。
2、const与let一样, 只在声明所在的块级作用域内有效
3、const命令声明的常量不存在提升
4、const命令声明的常量同样存在暂时性死区,只能在声明的位置后面使用。
5、const声明的常量,也与let一样不可重复声明
本质:const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。
简单数据类型( 数值、字符串、布尔值): 值就保存在变量指向的那个内存地址,因此等同于常量
复合数据类型( 对象和数组) : 变量指向的内存地址,保存的只是一个指向实际数据的指针 , const只能保证这个指针是固定的 至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
一个简单的改写案例
let add1 = function(a,b){
return a+b
}
let add2 = (a,b)=>{
return a+b
}
console.log(add1(1,2)); //3
console.log(add2(2,3)); //5
箭头函数中,this是静态的,始终指向函数声明时,所在作用域下this的值,所以箭头函数适合与 this 无关的回调
function getname(){
console.log(this.name);
}
getname2 = ()=>{
console.log(this.name);
}
//设置 window 对象的 name 属性
window.name = 'window'
//设置一个对象中的 name 属性
const obj = {
name:'object'
}
//直接调用
getname() //window
getname2() //window
//使用 call 方法改变
//call 方法,可以可以编写能够在不同对象上使用的方法,用来调用所有者对象作为参数的方法
getname.call(obj) //object
getname2.call(obj) //window
let person = (name,age)=>{
this.name = name
this.age = age
}
let p = new person('pzw',20)
//报错:Uncaught TypeError: person is not a constructor
很好理解,因为箭头函数的this指向始终是静态的,所以无法成为一个构造器
let fn = ()=>{
console.log(arguments)
}
fn(1,2,3)
//报错:Uncaught ReferenceError: arguments is not defined
let fn = (...rest)=>{
console.log(rest)
}
fn(1,2,3) //[1,2,3]
let pow = n => n*n
console.log(pow(3)) //9
扩展运算符( spread )是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,例如:
console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
[...document.querySelectorAll('div')]
// [, , ]
function push(array, ...items) {
array.push(...items);
}
function add(x, y) {
return x + y;
}
var numbers = [4, 38];
add(...numbers) // 42
替代数组的 apply 方法
// ES5 的写法
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);
解构赋值
灵活的数组解构赋值
let arr = [1,2,3,4,5,6]
let va = arr[4]
let [,a,b,c,...rest] = arr
console.log(a,b,c,rest) //2,3,4,[5,6]
对象的解构赋值
let p1 = {
"name":"zhuangzhuang",
"age":25
}
let {name,age} = p1;//注意变量必须为属性名
console.log(name,age);//"zhuangzhuang",25
在react中会经常用到
const { cur } = this.state
模板字符串
键盘Tab上的键 ``
ES6 引入新的声明字符串的方式 , 对比 ‘’, “” 的优点:
1、内容可直接换行,不用再使用 ‘+’ 拼接
2、可以直接拼接变量
let white = '小白';
let cat = `
- 你好,
${white}
`;
console.log(cat);
直接输出:
<ul>
<li>你好,${white}</li>
</ul>
rest参数
rest 参数,用于获取函数的实参,代替 arguments
当我们使用 arguments 时:
function date() {
console.log(arguments);
console.log(typeof arguments);
}
date(1, 2, 3, 4);
可以看到 arguments 原型中 constructor 属性为 Object,这就说明了 arguments 并不是数组,因此不能使用数组的方法,因此在处理多个参数时,并不能使用更加方便的数组方法进行处理。
接下来,我们再看一下 rest 获取参数的类型:
function date(...rest) {
console.log(rest);
console.log(typeof rest);
}
date(1, 2, 3, 4);
//打印[1, 2, 3, 4]
迭代器(Iterator)
首先说说for in,for of
简单来说
- for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。
- for-in总是得到对象的key或数组、字符串的下标。
- for-of总是得到对象的value或数组、字符串的值,另外还可以用于遍历Map和Set。
const arr = ['red', 'black', 'blue']
for(let i of arr) {
console.log(i) // 'red' 'black' 'blue'
}
for(let i in arr) {
console.log(i) // 1, 2, 3
}
如何使用 for…of 遍历自定义的数组呢,比如对象中不包含 迭代器(Iterator),但是可以自定义一个迭代器。
// 直接使用 for...of 遍历对象
const obj = {
arr: ['red', 'blue', 'green']
}
for(let i of obj) {
console.log(i) // Uncaught TypeError: obj is not iterable
}
// 给对象添加一个迭代器
const obj = {
color: ['red', 'blue', 'green'],
[Symbol.iterator]() {
let index = 0
return {
next: () => {
if (index < this.color.length) {
const res = { value: this.color[index], done: false }
index++
return res
}else {
return { value: undefined, done: true }
}
}
}
}
}
for (let i of obj) {
console.log(i) // 'red' 'blue', 'green'
}
生成器(generator)
在 ES6 中定义一个生成器函数很简单,在 function 后跟上「*」即可:
function* myGenerator() {
yield 'hello';
yield 'world';
return 'Generator';
}
var g = myGenerator();
g.next(); // { value: 'hello', done: false }
g.next(); // { value: 'world', done: false }
g.next(); // { value: 'ending', done: true }
g.next(); // { value: undefined, done: true }
调用生成器函数会产生一个生成器(generator)。生成器拥有的最重要的方法是 next(),用来迭代。
yield 主要用作代码隔断,生成器(generator)在执行的时候必须使用生成器(iterator)的next() 迭代。一个 next() 只能迭代一段 yield 代码。
我所理解的yield,就是程序执行的一个断点,用来暂停和继续一个生成器函数。
最后一次调用,Generator函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true
实现斐波那契数列例子
function* fab(max) {
var count = 0, last = 0, current = 1;
while(max > count++) {
yield current;
var tmp = current;
current += last;
last = tmp;
}
}
var o = fab(10), ret, result = [];
while(!(ret = o.next()).done) {
result.push(ret.value);
}
console.log(result); // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
Symbol 新增数据类型
ES6 新引入的基础数据类型,表示独一无二的值,是一种类似于字符串的类型。
第七种基本数据类型(Undefined、Null、Boolean、Number,String,Object,Symbol )
Symbol 特点
值是唯一的,用来解决命名冲突
- 不能与其他数据类型进行运算
- 定义的对象属性,不能使用 for···in 遍历,但是可以用Reflect.ownKeys 来获取对象的所有键名
简单实例
const s1 = Symbol('1')
// 或者:
const s2 = Symbol.for('1')
// 给对象添加 Symbol 类型的属性,保护对象属性的安全性
const obj = {
name: 'xiaobai',
[Symbol('say')]: function() {
console.log('说话')
}
}
obj.[Symbol('say')]()
// 或者
const obj1 = {
up: '',
dowm: ''
}
const methods = {
up: Symbol(),
dowm: Symbol()
}
obj[methods.up] = function() {
}
obj[methods.down] = function() {
}
// 这样创建的 up 和 dowm 方法是唯一的,不会和obj1中的up,dowm 属性发生冲突,保护了属性的安全。
Promise
在开始使用Promise之前,我们首先需要了解Promise的三种状态:
- pending: 初始状态,既不是成功,也不是失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
pending 状态的 Promise 对象可能会变为fulfilled 状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。
因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回promise 对象, 所以它们可以被链式调用。
一个Promise的基本案例
let p = new Promise((resolve,reject)=>{
setTimeout(() => {
let data = '数据'
resolve(data)
// let err = '数据读取错误'
// reject(err)
}, 1000)
}).then((data)=>{
console.log(data);
},(err)=>{
console.log(err);
})
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
Resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
Reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
Promise 封装读取文件
var fs = require('fs')
let readfile = new Promise((res,rej)=>{
fs.readFile('./file.md','utf-8',(err,data)=>{
if (err) rej(err)
res(data)
})
}).then((data)=>{
console.log(data.toString());
},(err)=>{
console.log('读取失败');
})
Promise封装 Ajax请求
let getajax = new Promise((res,rej)=>{
const xhr = new XMLHttpRequest()
xhr.open('GET','https://www.baidu.com')
xhr.send()
xhr.onreadystatechange = ()=>{
if (xhr.readyState === 4){
if (xhr.state >= 200 && xhr.state <= 299){
res(xhr.response)
}else{
rej(xhr.status)
}
}
}
}).then(data=>{
console.log(data);
},err=>{
console.error(err);
})
catch语法糖
getajax.catch(err =>{
console.log(err)
})
Set集合
ES6 新的数据结构 Set(集合),虽然类似于数组,但是成员的每一个值都是唯一的。集合实现了迭代器(iterator)接口,所以可以使用扩展运算符(…)和for…of进行遍历(让我想到C++的STL的库= 。=)。
集合的属性和方法:
- size:返回集合的元素个数
- add:增加一个新元素,返回当前集合
- delete:删除元素,返回一个Boolean值
- has:检测集合中是否包含某个元素,返回Boolean值
- clear:清空集合
const set = new Set(['red', 'blue', 'green'])
const res1 = set.has('red')
console.log(res1) // true
const r2 = set.size
console.log(r2) // 3
set.add('white')
console.log(set) // Set(4) {"red", "blue", "green", "white"}
set.delete('red')
console.log(set) // Set(3) {"blue", "green", "white"}
set.clear()
console.log(set) // Set(0) {}
Set集合的应用:
数组去重
const arr1 = [1, 2, 3, 3, 4, 2, 5]
const res1 = [...new Set(arr1)]
console.log(res1) // [1, 2, 3, 4, 5]
两个数组取交集
const arr1 = [1, 2, 3, 3, 4, 2, 5]
const arr2 = [4, 5, 6, 7, 5, 6]
const res2 = [...new Set(arr1)].filter(item => {
const s = new Set(arr2)
if(s.has(item)) {
return true
}else {
return false
}
})
console.log(res2) // [4, 5]
// 或者
const res3 = [...new Set(arr1)].filter(item => new Set(arr2).has(item))
console.log(res3) // [4, 5]
两个数组并集(数组合并)
const arr1 = [1, 2, 3, 3, 4, 2, 5]
const arr2 = [4, 5, 6, 7, 5, 6]
const res4 = [...new Set(arr1), ...new Set(arr2)]
console.log(res4) // [1, 2, 3, 4, 5, 4, 5, 6, 7]
数组差集
const arr1 = [1, 2, 3, 3, 4, 2, 5]
const arr2 = [4, 5, 6, 7, 5, 6]
const res5 = [...new Set(arr1)].filter(item => !new Set(arr2).has(item))
console.log(res5) // [1, 2, 3]
Map
ES6 新的数据结构。类似于对象,键值对的集合。但是‘键’的范围不限于字符串。各种类型的值(包括对象)都可以当做键。Map 也实现了 iterator 接口,可以使用扩展运算符(…)和for…of遍历(又是一个STL类型)
Map 的属性和结构
- size:返回Map元素的个数
- set:增加一个新元素,返回当前 Map
- get:返回键名对象的键值
- has:检测 Map 中是否含某个元素,返回Boolean值
- clear:清空集合
使用
const map = new Map()
map.set('name', 'zhangsan')
map.set('age', 18)
map.set('say', function() {
console.log('hello')
})
console.log(map) // {"name" => "zhangsan", "age" => 18, "say" => ƒ}
console.log(map.size) // 3
const res1 = map.get('name')
console.log(res1) // '张三'
const res2 = map.has('age')
console.log(res2) // true
const res3 = map.delete('say')
console.log(res3) // true
class
概述
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用ES6的“类”改写,就是下面这样
class stu{
static school = 'HZNU' //还拥有static 静态属性
//静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性
constructor(name,age){
this.name = name
this.age = age
}
print(){
console.log('我的名字是'+this.name+',我的年龄是'+this.age+',我在'+stu.school+'上学');
}
}
let stu1 = new stu('pzw',21)
stu1.print() //我的名字是pzw,我的年龄是21,我在HZNU上学
类的继承
class stu13 extends stu{
static school = 'GDZX'
static banji = 13
constructor(name,age,bzr){
super(name,age)
this.bzr = bzr
}
print13(){
console.log('我的名字是'+this.name+',我的年龄是'+this.age+',我在'+stu13.school+'上学,班级是'+stu13.banji+'班,我的班主任是'+this.bzr);
}
}
let stu2 = new stu13('hhb',21,'ZXY')
stu2.print13() //我的名字是hhb,我的年龄是21,我在GDZX上学,班级是13班,我的班主任是ZXY
get set
在类里面可以去定义一些getter和setter,getter可以得到一些东西的方法,setter可以设置东西
class classP{
constructor(){
this.arr = []
}
get queue(){
return this.arr
}
set queue(name){
this.arr.push(name)
}
}
let class13 = new classP()
class13.queue = 'pzw'
class13.queue = 'hhb'
console.log(class13.queue) //["pzw", "hhb"]
对象(Object)方法的扩展
Object.is
判断两个值是否相等,相当于全等于(===),但是NaN 和 (-0, 0)判断结果两者不相等
console.log(Object.is(123, 123)) // true
console.log(Object.is('red', 'red')) // true
console.log(Object.is(NaN, NaN)) // true ===结果为false
console.log(Object.is(-0, 0)) // false ===结果为true
下图所示,展示了使用==,===和Object.is的结果差异:
Object的遍历方法
const obj = {
book: "Learning ES2017 (ES8)",
author: "前端达人",
publisher: "前端达人",
useful: true
};
console.log(Object.keys(obj)) // [ 'book', 'author', 'publisher', 'useful' ]
console.log(Object.values(obj)) //[ 'Learning ES2017 (ES8)', '前端达人', '前端达人', true ]
console.log(Object.entries(obj))
/*
[
[ 'book', 'Learning ES2017 (ES8)' ],
[ 'author', '前端达人' ],
[ 'publisher', '前端达人' ],
[ 'useful', true ]
]*/
//Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。
const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
Object.fromEntries(entries) // { foo: "bar", baz: 42 }
Object.assign
对象合并,后一个对象中的属性值会覆盖前一个对象上的属性值,如果前一个对象中不存在后一个对象中的某个属性,就会在对象中添加这个属性。
const data1 = {
name: 'zhangsan',
age: 15,
color: 'red'
}
const data2 = {
name: 'zhangsan',
age: 15,
color: 'blue',
height: 60
}
const data = Object.assign(data1, data2)
console.log(data) // {name: "zhangsan", age: 15, color: "blue", height: 60}
数组(Array)方法扩展
includes
includes 方法用来检测数组中是否包含某个元素,返回 Boolean 值
const arr = ['red', 'blue', 'green', 'white']
console.log(arr.includes('red')) // true
console.log(arr.includes('black')) // false
flat 和 flatMap
const arr1 = [1, 2, 3, [4, 5, 6, [7, 8]]]
console.log(arr1.flat()) // [1, 2, 3, 4, 5, 6, [7, 8]]
// 传参表示深度
console.log(arr1.flat(2)) // // [1, 2, 3, 4, 5, 6, 7, 8]
map
map()方法定义在JavaScript的Array中,它返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。
let numbers = [4, 9, 16, 25]
let sq = numbers.map(value=>{
return Math.sqrt(value)
})
console.log(sq); //[ 2, 3, 4, 5 ]
map会对数组的每一项进行处理,返回新数组,返回的新数组包含对之前每一项处理结果;
filter
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
let numbers = [4, 9, 16, 25]
let sq = numbers.filter(value=>{
return value === 16
})
console.log(sq) //[ 16 ]
every
依据判断条件,数组的元素是否全满足,若满足则返回true
let arr = [1,2,3,4,5]
let arr1 = arr.every( (value, index) =>value<3)
console.log(arr1) // false
let arr2 = arr.every( (value, index) =>value<6)
console.log(arr2) // true
reduce
arr.reduce(callback, initialValue) 迭代数组的所有项,累加器,数组中的每个值(从左到右)合并,最终计算为一个值
参数: callback: previousValue 必选 --上一次调用回调返回的值
currentValue 必选 --数组中当前被处理的数组项
index 可选 --当前数组项在数组中的索引值
array 可选 --原数组
initialValue: 可选 --初始值
let arr = [0,1,2,3,4]
let arr1 = arr.reduce((preValue, curValue) =>
preValue + curValue
)
console.log(arr1) // 10
reduce的高级用法
(1)计算数组中每个元素出现的次数
let names = ['peter', 'tom', 'mary', 'bob', 'tom','peter'];
let nameNum = names.reduce((pre,cur)=>{
if(cur in pre){
pre[cur]++
}else{
pre[cur] = 1
}
return pre
},{})
console.log(nameNum); //{ peter: 2, tom: 2, mary: 1, bob: 1 }
(2)数组去重
let arr = [1,2,3,4,4,1]
let newArr = arr.reduce((pre,cur)=>{
if(!pre.includes(cur)){
pre.push(cur)
}
return pre
},[])
console.log(newArr);// [1, 2, 3, 4]
arr.reduceRight(callback, initialValue) 与arr.reduce()功能一样,不同的是,reduceRight()从数组的末尾向前将数组中的数组项做累加。
模块化
模块化是指将一个大的程序文件,拆分成为许多小的文件,然后将文件组合起来。
模块化的好处
- 防止命名冲突
- 代码复用
- 高维护性
ES6模块化语法
模块功能主要由两个命令构成:export和import
- export命令用于规定模块对外的接口
- import命令用于输入其他模块提供的功能
默认导入导出
默认导出语法 export default 默认导出成员
//当前文件模块为ml.js
//定义私有成员a和c
let a = 10
let c = 20
//外界访问不到变量d 因为它没有被暴露出去
let d = 30
function show(){
//将本模块中的私有成员暴露出去 供其他模块使用
export default{
a,
c,
show
}
}
默认导入语法 import 接受名称 from ‘模块标识符’
//导入模块成员
import ml from '.ml.js'
console.log(ml)
//打印结果为
//{a:10, c:20, show:[]function:show]}
按需导出 与 按需导入
按需导出语法 export let s1 = 10
//当前文件模块为m1.js
//向外按需导出变量 s1
export let s1 = 'aaa'
//向外按需导出变量s2
export let s2 = 'bbb'
//向外按需导出方法 say
export function say = function(){}
按需导入语法 import { s1 } from ‘模块标识符’
//导入模块成员
import {s1, s2 as ss2, say} from './m1.js'
console.log(s1) //打印输出aaa
console.log(s2) //打印输出bbb
console.log(say) //打印输出[function:say]
async 与 await
async 和 await 两种语法结合,可以让异步代码看起来像同步代码。
async
async 函数返回值为 promise 对象
promise 对象结果由 async 函数执行的返回值决定
await
await 函数必须写在 async 函数中
await 右侧的表达式一般为 promise 对象
await 返回的是 promise 的成功值
await 的 promise 失败了,就会抛出异常,需要通过 try…catch 捕获处理
读取文件
const fs = require('fs')
let p1 = new Promise((res,rej)=>{
fs.readFile('./file.md',(err,data)=>{
if (err) rej(err)
res(data)
})
})
let p2 = new Promise((res,rej)=>{
fs.readFile('./file2.md',(err,data)=>{
if (err) rej(err)
res(data)
})
})
async function fileread(){
let n1 = await p1
let n2 = await p2
console.log(n1.toString()) //我是文件1里的数据
console.log(n2.toString()) //我是文件2里的数据
}
fileread()
Ajax
function Ajax(type='GET', url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open(type, url)
xhr.send()
xhr.onreadystatechange = function () {
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response)
}else {
reject(xhr.status)
}
}
}
})
}
async function xmlajax() {
const res1 = await Ajax('GET', 'https://www.baidu.com')
const res2 = await Ajax('GET', 'https://www.sina.com')
console.log(res1)
console.log(res2)
}
xmlajax()
可选链操作符
?. 当对象层数比较深,使用可选链操作符,可以免去层级判断。
如果不使用可选链操作符,一般使用 && 来连接判断
let data = {
d1:{
d2:{
id:'123'
}
}
}
let db = data && data.d1 && data.d1.d2 && data.d1.d2.id
console.log(db) //123
let content = data?.d1?.d2?.id
console.log(content) //123
参考文章:
https://segmentfault.com/a/1190000020934044
https://segmentfault.com/a/1190000020889508
https://www.cnblogs.com/hexiaobao/p/12108572.html