let 关键字
let
关键字用来声明变量,使用 let
声明的变量有几个特点:
- 不允许重复声明
- 块级作用域
- 不存在变量提升
- 不影响作用域链
经典案例 demo
应用场景:以后声明变量使用 let 就对了
const 关键字
const
关键字用来声明常量,const
声明有以下特点
- 和
let
一样也没有声明提前 - 声明必须赋初始值
- 标识符一般为大写(并不是语法要求)
- 不允许重复声明
- 除引用类型外,值不允许修改
- 块级作用域
应用场景:声明引用类型使用 const
,非对象类型声明选择 let
《JavaScript 中变量声明 var、let、const 的区别》中有较为详细的描述
变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。
- 数组解构赋值:
当对数组解构赋值时,需要使用中括号
const arr = ['张学友', '刘德华', '黎明', '郭富城'];
// 每一个变量都对应 arr 中的值
let [zhang, liu, li, guo] = arr;
- 对象的解构赋值
当对对象解构赋值时,需要使用花括号
const lin = {
name: '林志颖',
tags: ['车手', '歌手', '小旋风', '演员']
};
let { name, tags } = lin;
注意:解构对象的时候,变量必须是对象属性的值,如果想修改变量名可以使用 :
const lin = {
name: '林志颖',
tags: ['车手', '歌手', '小旋风', '演员']
};
let { name: n, tags } = lin;
- 复杂解构
let wangfei = {
name: "王菲",
age: 18,
songs: ["红豆", "流年", "暧昧", "传奇"],
history: { name: "窦唯" },
};
let {
songs: [one, two, three],
history: { name },
} = wangfei;
应用场景:频繁使用对象方法、数组元素,就可以使用解构赋值形式
模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识,特点:
- 字符串中可以出现换行符
- 可以使用
${变量名}
形式输出变量
// 定义字符串
let str = `<ul>
<li>沈腾</li>
<li>玛丽</li>
<li>魏翔</li>
<li>艾伦</li>
</ul>`;
// 变量拼接
let star = '王宁';
let result = `${star}在前几年离开了开心麻花`;
应用场景:当遇到字符串与变量拼接的情况使用模板字符串
简化对象写法
ES6 中当对象的键和变量名或函数相同时, 允许在大括号里面直接写入变量和函数,作为对象的属性和方法。
let name = 'zs';
let improve = function () {
console.log('可以提高你的技能');
}
// 属性和方法简写
let atguigu = {
name,
improve,
change() {
console.log('可以改变你') }
}
应用场景:对象简写形式简化了代码,所以以后用简写就对了
箭头函数
ES6 允许使用「箭头」(=>)定义函数。
// 通用写法
let fn = (arg1, arg2, arg3) => {
return arg1 + arg2 + arg3;
}
箭头函数的特性:
- 箭头函数 this 指向声明时所在作用域下 this 的值
- 箭头函数声名之后不会更改 this 指向
- 箭头函数不能作为构造函数实例化
- 不能使用 arguments
- 没有原型 prototype 属性
应用场景:箭头函数声名之后不会更改 this 指向,用来指定回调函数会非常合适
《JavaScript 中变量声明 var、let、const 的区别》中有较为详细的描述
函数参数默认值
ES6 允许给函数参数赋值初始值
function add (a, b, c = 10) {
return a + b + c
}
console.log(add(1, 2)) // 13
通常会与解构赋值结合使用
function connect ({ host, username, password, port = 3306 }) {
console.log(host)
console.log(username)
console.log(password)
console.log(port)
}
connect({
host: 'baidu.com',
username: 'root',
password: 'root',
})
注意:当给形参赋初始值时,一般位置要靠后,因为形参接收参数是根据传入顺序来的,否则将毫无意义
rest 参数
ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments。与 arguments 不同的是,rest 参数是一个数组,语法:(…变量名)
// 作用与 arguments 类似
function add (...args) {
console.log(args)
}
add(1, 2, 3, 4, 5)
// 当有多个参数时,rest 参数必须是最后一个形参
function minus (a, b, ...args) {
console.log(a) // 1
console.log(b) // 2
console.log(args) // [3, 4, 5, 6]
}
minus(1, 2, 3, 4, 5, 6)
应用场景:rest 参数非常适合不定个数参数函数的场景
注意:rest 参数必须放到形参最后
spread 扩展运算符
扩展运算符(spread)也是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包
// 展开数组
let heroes = ['德玛西亚之力', '德玛西亚之翼', '德玛西亚皇子']
function fn () {
console.log(arguments)
}
fn(...heroes)
// 展开对象
let skillOne = {
q: "致命打击"
}
let skillTwo = {
w: "勇气"
}
let gailun = { ...skillOne, ...skillTwo }
console.log(gailun) // { q:"致命打击", w: "勇气" }
应用场景:
// 数组的合并
const kuaizi = ['王太利', '肖央'];
const fenghuang = ['王太利', '肖央'];
console.log([...kuaizi, ...fenghuang]) // ['王太利', '肖央', '王太利', '肖央']
// 数组的拷贝,如果数组中有引用类型时,是一个浅拷贝
const sanzhihua = ['E', 'G', 'M']
const sanyecao = [...sanzhihua]
// 将类数组转换为真正的数组
const divs = documents.querySelectorAll("div");
const divArr = [...divs]
Symbol
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。
Symbol 特点:
- Symbol 的值是唯一的,用来解决命名冲突的问题
- Symbol 值不能与其他数据进行运算
- Symbol 定义的对象属性不能使用 for…in 循环遍历,但是可以使用
Reflect.ownKeys
来获取对象的所有键名
Symbol 基本使用
Symbol 值通过 Symbol
函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。
Symbol
函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
// 创建 Symbol
let s1 = Symbol();
console.log(s1, typeof s1);
// 添加标识的 Symbol
let s2 = Symbol('zs');
let s2_2 = Symbol('zs');
console.log(s2 === s2_2); // false
// 使用 Symbol for 定义
let s3 = Symbol.for('zs');
let s3_2 = Symbol.for('zs');
console.log(s3 === s3_2); // true
Symbol 在对象中使用:
let s = Symbol();
let obj = {
[s](arg) { ... }
};
// 或者
let obj = {
[Symbol()](arg) { ... }
};
应用场景: 遇到唯一性的场景时要想到 Symbol
注意:Symbol 值作为对象属性名时,不能用点运算符
Symbol 内置值
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。
方法 | 描述 |
---|---|
Symbol.hasInstance | 当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法 |
Symbol.isConcatSpreadable | 对象的 Symbol.isConcatSpreadable 属性等于的是一个 布尔值,表示该对象用于 Array.prototype.concat() 时, 是否可以展开。 |
Symbol.species | 创建衍生对象时,会使用该属性 |
Symbol.match | 当执行 str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。 |
Symbol.replace | 当该对象被 str.replace(myObject) 方法调用时,会返回该方法的返回值。 |
Symbol.search | 当该对象被 str. search(myObject) 方法调用时,会返回该方法的返回值。 |
Symbol.split | 当该对象被 str.split(myObject) 方法调用时,会返回该方法的返回值。 |
Symbol.iterator | 对象进行 for…of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器 |
Symbol.toPrimitive | 该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 |
Symbol. toStringTag | 在该对象上面调用 toString 方法时,返回该方法的返回值 |
Symbol. unscopables | 该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除。 |
迭代器(Iterator)
迭代器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
- ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费
- 原生具备 iterator 接口的数据(可用 for of 遍历)
- Array
- Arguments
- Set
- Map
- String
- TypedArray
- NodeList
- 工作原理
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
- 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
- 每调用 next 方法返回一个包含 value(当前成员的值)和 done(表示遍历是否结束)属性的对象
应用案例:
const obj = {
name: "zs",
age: 18,
[Symbol.iterator]: function () {
let nextIndex = 0;
let _this = this;
return {
next: function () {
return nextIndex < Object.keys(obj).length
? { value: Object.keys(obj)[nextIndex++], done: false }
: { valeu: undefined, done: true };
},
};
},
};
// 完成了对一个对象的遍历
for (const v of obj) {
console.log(v);
}
应用场景: 需要自定义遍历数据的时候,要想到迭代器。
生成器(generator)
生成器函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
function * gen(){
yield '一只没有耳朵';
yield '一只没有尾巴';
return '真奇怪';
}
let iterator = gen();
console.log(iterator.next()); // {value: '一只没有耳朵', done: false}
console.log(iterator.next()); // {value: '一只没有尾巴', done: false}
console.log(iterator.next()); // {value: '真奇怪', done: true}
代码说明:
- 的位置没有限制
- 生成器函数返回的结果是迭代器对象,调用迭代器对象的 next 方法可以得到 yield 语句后的值
- yield 相当于函数的暂停标记,也可以认为是函数的分隔符,每调用一次 next 方法,执行一段代码
- next 方法可以传递实参,作为 yield 语句的返回值
应用案例:
要求:一秒后输出 111,两秒后输出 222,三秒后输出 333
function getUsers () {
setTimeout(() => {
let data = '用户数据'
iterator.next(data)
}, 1000)
}
function getOrders () {
setTimeout(() => {
let data = '订单数据'
iterator.next(data)
}, 2000)
}
function getGoods () {
setTimeout(() => {
let data = '商品数据'
iterator.next(data)
}, 3000)
}
function * gen () {
let users = yield one()
console.log(users) // 用户数据
let orders = yield two()
console.log(orders) // 订单数据
let goods = yield three()
console.log(goods) // 商品数据
}
const iterator = gen()
iterator.next()
Promise
关于 Promise 可以阅读此文章
Set
ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历,集合的属性和方法:
- size 返回集合的元素个数
- add 增加一个新元素,返回当前集合
- delete 删除元素,返回 boolean 值
- has 检测集合中是否包含某个元素,返回 boolean 值
- clear 清空集合,返回 undefined
// 创建一个空集合
let s = new Set();
// 创建一个非空集合
let s1 = new Set([1,2,3,1,2,3]);
// 集合属性与方法
// 返回集合的元素个数
console.log(s1.size); // 3
// 添加新元素
console.log(s1.add(4)); // Set(4) {1, 2, 3, 4}
// 删除元素
console.log(s1.delete(1)); // true
// 检测是否存在某个值
console.log(s1.has(2)); // true
// 清空集合
console.log(s1.clear());
应用案例
let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1]
let arr2 = [4, 5, 6, 5, 6]
// 1.数组去重
let result = [...new Set(arr)]
console.log(result)
// 2.交集
let result1 = [...new Set(arr)].filter(item => new Set(arr2).has(item))
// 3.并集
let union = [...new Set([...arr, ...arr2])]
// 4.差集
let diff = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)))
注意:Set 会将传入的参数自动去重
Map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键” 的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。Map 的属性和方法:
- size 返回 Map 的元素个数
- set 增加一个新元素,返回当前 Map
- get 返回键名对象的键值
- has 检测 Map 中是否包含某个元素,返回 boolean 值
- clear 清空集合,返回 undefined
// 创建一个空 map
let m = new Map();
// 创建一个非空集合
let m2 = new Map([
['name','zs'],
['slogon','不断提高行业标准']
]);
//属性和方法
//获取映射元素的个数
console.log(m2.size);
//添加映射值
console.log(m2.set('age', 6));
//获取映射值
console.log(m2.get('age'));
//检测是否有该映射
console.log(m2.has('age'));
//清除
console.log(m2.clear());
Class
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是 一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。知识点:
- class 声明类
- constructor 定义构造函数初始化
- extends 继承父类
- super 调用父级构造方法
- static 定义静态方法和属性
- 父类方法可以重写
// 父类
class Phone {
// 构造方法
constructor(brand, color, price) {
this.brand = brand;
this.color = color;
this.price = price;
}
// 对象方法
call() {
console.log('我可以打电话!!!')
}
}
// 子类
class SmartPhone extends Phone {
constructor(brand, color, price, screen, pixel) {
super(brand, color, price);
this.screen = screen;
this.pixel = pixel;
}
// 子类方法
photo(){
console.log('我可以拍照!!');
}
playGame(){
console.log('我可以玩游戏!!');
}
// 方法重写
call(){
console.log('我可以进行视频通话!!');
}
// 静态方法
static run(){
console.log('我可以运行程序')
}
static connect(){
console.log('我可以建立连接')
}
}
// 实例化对象
const Nokia = new Phone('诺基亚', '灰色', 230);
const iPhone6s = new SmartPhone('苹果', '白色', 6088, '4.7inch','500w');
// 调用子类方法
iPhone6s.playGame();
// 调用重写方法
iPhone6s.call();
// 调用静态方法
SmartPhone.run();
class 中的 getter 和 setter,当属性被获取或修改时会被触发
class Phone {
get price() {
console.log("价格属性被读取了");
return 20
}
set price(newVal) {
console.log("价格属性被修改了");
console.log(newVal); // 30
}
}
let s = new Phone()
console.log(s.price);
s.price = 30
数值扩展
Number
Number.EPSILON
是 JavaScript 中表示的最小精度,值接近于 2.2204460492503130808472633361816E-16
,或者 2-52
。
该值可以用于浮点数的计算
function numbersequal(a,b){
return Math.abs(a - b) < Number.EPSILON;
}
var a = 0.1 + 0.2, b = 0.3;
console.log(numbersequal(a,b)); // true
Number.isFinite()
用来检查一个数值是否为有限的
Number.isNaN()
用来检查一个值是否为 NaN
ES6 将全局方法 parseInt
和 parseFloat
,移植到 Number 对象上面,使用不变。
Number.isInteger()
用来判断一个数值是否为整数
Math
Math.trunc()
用于去除一个数的小数部分,返回整数部分。
Math.sign()
判断一个数到底为正数、负数不是零
进制表示
ES6 提供了二进制、八进制和十六进制数值的新的写法,分别用前缀 0b、0o 和 0x 表示。
// 二进制
let b = 0b1010;
// 八进制
let o = 0o777;
// 十进制
let d = 100;
// 十六进制
let x = 0x;
对象扩展
ES6 新增了一些 Object 对象的方法
Object.is()
比较两个值是否严格相等,与『===』行为基本一致(NaN 使用全等判断为 false)Object.assign()
对象的合并,将源对象的所有可枚举属性,复制到目标对象(浅拷贝)__proto__
、Object.setPrototypeOf()
、Object.``setPrototypeOf
可以直接设置对象的原型
模块化
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。
模块化的好处
模块化的优势有以下几点:
- 防止命名冲突
- 代码复用
- 高维护性
模块化规范产品
ES6 之前的模块化规范有:
- CommonJS => NodeJS、Browserify
- AMD => requireJS
- CMD => seaJS
ES6 模块化语法
模块功能主要由两个命令构成:export 和 import。
- export 命令用于规定模块的对外接口
let age = 18;
let obj = {
name: 'ls'
}
// 分别导出
export let name = 'zs';
export function sayHello() {
console.log('Hello World')
}
// 统一导出
export {
age,
obj
}
// 默认导出
export default {
name: 'ww',
age: 20
}
- import 命令用于输入其他模块提供的功能
<script type="module">
// 全部导入
import * as m1 from "./src/js/test.js";
// 解构形式
import {name, age} from './test.js';
// 使用 as 防止冲突
import {name as zs, age as nl} from './test.js';
// 解构默认导出
import {default as m3} from './test.js';
// 简写形式,只针对默认导出
import m3 from './test.js';
// 如果使用的是默认导出方式,使用需要加上 .defalut
console.log(m1.default.name)
</script>
使用 babel
- 安装工具 babel-cli babel-preset-env browserify(项目中通常使用webpack)
- 文件转换 npx babel src/js -d dist/js —presets=babel-preset-env
- 打包 npx browserify dist/js/app.js -o dist/bundle.js