ECMA Script 6
ECMA Script 6(ES2016)学习笔记
TIP
最后更新时间:2019年09月21日
字数:18029
ES6
- ECMA-262 可以称之为:ES1.0
- ECMA2015 -> ES6
- ECMA每年6月份发布一个版本,(不一定是大版本,所以有时候叫ES7也不合适)
- ESNext:下一代的ES开发语言,或者称之为下一个版本的开发语言
学习资料
- 阮一峰ES6入门 强烈推荐:小峰哥还买了实体书支持了一下!
Stage-n
提案变成标准需要经过五个阶段
- stage 0 :展示阶段
- stage 1 :征求意见阶段
- stage 2 :草案阶段(bebel配置,一般都是stage2,其实就是意味着可以使用的阶段)
- stage 3 :候选阶段
- stage 4 :定案阶段(标准)
TIP
Ecma TC39委员会(任何人都可以提es任何意见和建议哦):快捷连接
ES6之前变量
全局作用域
- 在任何地方都可以访问的变量,就是全局变量
- 全局变量生效的作用域,就是全局作用域
var a = "全局";
function fn(){
console.log(a);
}
fn();//结果:全局
2
3
4
5
函数作用域
- 只能在函数内部访问的变量就是局部变量
- 对应的作用域就是局部作用域(又称函数作用域)
- 因为js只有函数可以划分作用域(当前阶段ES5)
function fn(){
var a = "局部";
}
fn();
console.log(a);// ReferenceError: a is not defined
function fn1() {
b = '不带var'
}
fn1()
console.log(b); // 不带var
2
3
4
5
6
7
8
9
10
11
TIP
函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
ES6作用域
块级作用域
- ES6引入了块级作用域,明确允许在块级作用域中声明函数
- 块级作用域中,函数声明语句的行为类似于let,在块级作用域之外,不可以引用
- 块级作用域用一对花括号来表示
// ES5之前模拟块级作用域:匿名函数自调用,LLFE,立即执行函数
(function(){
// TODO
})()
{
// 这里是块级作用域
}
{{ // 块级作用域可以嵌套 }}
2
3
4
5
6
7
8
9
10
let
相当于之前的var
const
- const翻译过来是常量的意思
- 所谓常量,就是定义好了,就不能改变了
- const声明的是一个只读常量,在声明时就需要赋值
- 如果
const
的是一个对象,对象所包含的值是可以被修改的 - 抽象一点儿说,就是对象所指向的地址不能改变,而变量成员是可以修改的
- 如果
const a = 12
const a = 14
console.log(a)
// Identifier 'a' has already been declared
// 不允许重重复定义
2
3
4
5
const a = 12
a = 14
console.log(a)
// Assignment to constant variable
// 就是定义好了,就不能改变了
2
3
4
5
const a = {
name:"zhangsan"
}
a.name = "lisi"
console.log(a) // {name: "lisi"}
// 如果 const 的是一个对象,对象所包含的值是可以被修改的
2
3
4
5
6
let和const特点
1. let
和 const
都是块级作用域,以{}代码块作为作用域范围 只能在代码块里面使用
if(1) {
var a = 12
console.log('a===',a) // a=== 12
}
console.log('a===',a) // a=== 12
if(1) {
let b = 14
const c = 16
console.log('b===',b) // b=== 14
console.log('c===',c) // c=== 16
}
console.log('b===',b) // b is not defined
console.log('c===',b) // c is not defined (这里其实是执行不到的,执行到也会报错)
2
3
4
5
6
7
8
9
10
11
12
13
14
TIP
let可以在{},if,for里声明,其用法同var,但是作用域限定在块级,也就是说出了块级作用域,let和const声明的变量是无法使用的,不是全局作用域
2. 不存在变量提升,只能先声明再使用,否则会报错
var a = 12
function fn() {
alert(a) // 12
}
fn()
// 这是正常情况下的,没有毛病,a全局作用域
2
3
4
5
6
var a = 12
function fn() {
alert(a) // undefined
var a = 20
}
fn()
//undefined 也是没有问题的,打印的a,会使用函数内部的a,但是因为变量提升,但是没有赋值,所以打印undefined
2
3
4
5
6
7
var a = 12
function fn() {
alert(a) // a is not defined
let a = 20
}
fn()
// 这里问题来了,他说a没有定义,可以说明:
// a没有使用外部的全局变量a,同时,let声明的a变量没有做变量提升
2
3
4
5
6
7
8
TIP
在代码块内,在声明变量之前,该变量都是不可用的
这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)
3. 在同一个代码块内,不允许重复声明
{
let a = 11
let a = 12 // Identifier 'a' has already been declared
{
let a = 13 // 可以,因为不在同一个块级作用域里面(大括号)
}
}
2
3
4
5
6
7
let的for循环问题
var a = []
for (let i =0; i<10; i++) {
a[i] = function () {
console.log(i)
}
}
a[6]() // 6
2
3
4
5
6
7
- 变量i是let声明的 当前i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量
- Javascript之所以知道上一轮循环的值,是因为javascript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算,而不是let声明的i自加
var arr = []
for (var i =0; i<10; i++) {
arr[i] = function () {
console.log(i)
}
}
arr[4]() // 10
for (let i =0; i<5; i++) {
let i = "xiaofengge"
console.log(i) // 打印五次小峰哥,而不是数字i,因为他们其实在不同的作用域
}
2
3
4
5
6
7
8
9
10
11
12
TIP
for循环还有一个特别之处,就是设置循环变量那部分是一个父级作用域,而循环体内部是一个单独的子作用域
Object.freeze()冻结一个对象
- Object.freeze()是ES5新增的特性,可以冻结一个对象,防止对象被修改
- 冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。防止对象被修改
- 如果你有一个巨大的数组或Object,并且确信数据不会修改,使用Object.freeze()可以让性能大幅提升
- Vuejs中可以使用提升性能,同时:Object.freeze()冻结的是值,你仍然可以将变量的引用替换掉,也就是重新赋值(一个不是Object.freeze()的值)
解构赋值
结构示例
let arr = ['11','22']
console.log(arr) // ["11", "22"]
let [a, b, c] = ['aaa','bbb','ccc']
console.log(a,b,c) // aaa bbb ccc 这就是最简单的解构赋值
let json = {
name: "zhangsan",
age: 18,
job: "CEO"
}
let { name, age, job } = json
console.log(name, age, job) // zhangsan 18 CEO
2
3
4
5
6
7
8
9
10
11
12
13
14
规定:解构赋值要求:左右两边,结构和格式要保持一致,否则有些数据会出问题
解构别名(冒号)
let json = {
name: "zhangsan",
age: 18,
job: "CEO"
}
let { name:n, age:a, job:j } = json
// 解构别名
console.log(n, a, j) // zhangsan 18 CEO
2
3
4
5
6
7
8
解构默认值
let [a1, b1, c1="暂无数据"] = ['aaa', 'bbb']
console.log(a1, b1, c1) // aaa bbb 暂无数据
let [a2, b2, c2="暂无数据"] = ['aaa', 'bbb', undefined]
console.log(a2, b2, c2) // aaa bbb 暂无数据
let [a3, b3, c3="暂无数据"] = ['aaa', 'bbb', null]
console.log(a3, b3,c3) // aaa bbb null
let json = {
name: "zhangsan"
}
let {name, age=18} = json
console.log(name, age) // zhangsan 18
2
3
4
5
6
7
8
9
10
11
12
TIP
- 解构赋值是可以设置默认值的
- 注意后台传回来的是null的时候,是有值,但是值是null,而不可以像undefined去取到默认值
解构应用
- import {a ,b , c } from 'xxx'
- 函数传参解构
- 数组交换顺序解构(比如交换2个变量的值: [a,b] = [b,a])
字符串模板
ES6新增字符串模板``
let name = "zhangsan"
let age = 18
// 我是zhangsan,我的年龄是18岁
console.log('我是'+name+',我的年龄是'+age+'岁')
// 我是zhangsan,我的年龄是18岁
console.log(`我是${name},我的年龄是${age}岁`)
2
3
4
5
6
7
8
includes
字符串或数组查找
- Str.includes() 和 Array.includes()
- indexOf()返回的是数值,而includes()返回的是布尔值
- indexOf() 不能判断NaN,返回为-1 ,includes()则可以判断
startWith
判断以xxx开头
- str.startWith()
endsWith
判断以xxx结尾
- str.endsWith()
let str = 'https://www.baidu.com'
console.log(str.startsWith('https')) // true
console.log(str.endsWith('.com')) // false
2
3
4
repeat
字符串重复次数
- str.repeat(次数)
- 参数次数必须大于等于0的整数,写小数会被默认取整的,不会报错
let str = '你好!'
// 重复三次(后面参数是次数,必须大于等于0)
console.log(str.repeat(3)) // 你好!你好!你好!
2
3
padStart和padEnd
- str.padStart(长度,需要填充的参数):往前填充
- str.padEnd(长度,需要填充的参数):在后面填充
函数默认参数
ES6中函数的默认参数是可以传递默认值的
function fn(a='你好',b="小峰哥") {
console.log(a,b)
}
fn() // 你好 小峰哥
fn('') // 小峰哥 (前面传递的空字符串)
// 解构联合函数默认参数
// 如果都不传,默认参数是:{}
function fn({ a = 0, b = 0 } = {}) {
console.log(a,b)
}
fn({a:1}) // 1 0
fn({b:2}) // 0 2
fn({c:3}) // 0 0
fn() // 0 0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TIP
函数的参数默认是已经定义好了,不能再用let和const去定义
function fn(a,b) {
let a = 13 // 报错: Identifier 'a' has already been declared
console.log(a,b)
}
fn()
2
3
4
5
扩展运算符(rest运算符)
写法:三个点 ...
- 扩展和重置
let arr = ['aaa','bbb','ccc']
console.log(arr) // 数组: ["aaa", "bbb", "ccc"]
console.log(...arr) // 数组被展开了: aaa bbb ccc
// ... 既可以展开,也可以重置
function fn(...a) {
console.log(a) // [1, 2, 3, 4, 5, 6] 数组
}
fn(1,2,3,4,5,6)
2
3
4
5
6
7
8
9
- 剩余
// ...c 就是剩余的
function fn(a, b, ...c) {
console.log(a,b) // 1 2
console.log(c) // 数组: [3, 4, 5, 6]
}
fn(1,2,3,4,5,6)
2
3
4
5
6
TIP
剩余必须放在最后,不允许放在中间,必须是最后一个参数
箭头函数
let fn = () => {
console.log('我是箭头函数')
}
fn() // 我是箭头函数
2
3
4
5
箭头函数如果有返回值,可以直接写在后面
let fn = () => '我是箭头函数!'
function fn1() {
return '我是箭头函数!!'
}
// 上下2个函数是等价的
console.log(fn()) // 我是箭头函数!
console.log(fn1()) // 我是箭头函数!!
2
3
4
5
6
7
8
9
TIP
箭头函数的this永远指向定义函数所在的对象,而不是运行时所在的对象
箭头函数的this永远指向其上下文的 this,任何方法都改变不了其指向,如call(), bind(), apply()
var name = "lisi"
let obj = {
name: 'zhangsan',
say: function () {
setTimeout( function () {
console.log(this.name) // lisi
}, 2000);
}
}
obj.say()
2
3
4
5
6
7
8
9
10
var name = "lisi"
let obj = {
name: 'zhangsan',
say: function () {
setTimeout(() => {
console.log(this.name) // zhangsan
}, 2000);
}
}
obj.say()
2
3
4
5
6
7
8
9
10
箭头函数不绑定arguments,取而代之用rest(...)参数
function fn() {
console.log(arguments)
}
fn(1,2,3)
let fn1 = () => {
console.log(arguments)
}
fn(1,2,3)
fn1(1,2,3) // 报错 arguments is not defined
let fn2 = (...c) => {
console.log(c)
}
fn2(1,2,3) // [1, 2, 3]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
箭头函数作为匿名函数,是不能作为构造函数的,不能使用new
var B = ()=>{
value:1;
}
var b = new B(); //TypeError: B is not a constructor
2
3
4
ES6数组扩展
新增运算符
幂运算符:****** (2个星号)
console.log(2 ** 3) // 8
ES6之前
循环
for
while
arr.forEach()
- 代替普通for
arr.map()
- 非常有用,做数据交互 "映射",重新整理数据结构
- 正常情况下,需要配合return,返回是一个新的数组
- 若是没有return,相当于forEach
- 注意:平时只要用map,一定是要有return
arr.filter()
- 过滤,过滤一些不合格“元素”, 如果回调函数返回true,就留下来
arr.some()
- 类似查找, 数组里面某一个元素符合条件,返回true
arr.every()
- 数组里面所有的元素都要符合条件,才返回true
arr.reduce()
- 从左往右
arr.reduceRight()
- 从右往左
ES6新增循环for...of...
- 遍历所有数据结构的统一的方法
- 使用范围:
- 数组
- Set
- Map
- 类数组
- 字符串
- arguments
- DOM Nodelist对象
- Generator
对象简洁语法
对象新增
Promise
简单来说,Promise 就是用同步的方式写异步的代码,用来解决回调(异步)问题
js单线程
- Javascript语言的执行环境是"单线程"(single thread),就是指一次只能完成一件任务
- 如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推
TIP
注意:
- 单线程和多线程的区别
- 线程和进程的区别
- js是单线程的,浏览器是多进程的
Promise之前
三种解决异步问题的方案:
- 回调函数(callback)
- 事件监听
- 发布/订阅模式(又称:观察者模式)
回调函数(callback)
- 调是一个函数被作为一个参数传递到另一个函数里,在那个函数执行完后再执行
function fn2(arg) {
console.log(arr)
}
function fn1(callback){
setTimeout(function () {
// f1的任务代码
let arr = [1,2,3]
callback(arr);
}, 1000);
}
// 执行
fn1(fn2) // 1秒后打印 arr
2
3
4
5
6
7
8
9
10
11
12
回调并不一定就是异步,他们没有直接关系,我们只是用回调来解决异步的问题
- 同步回调
- 很简单,就是链式同步执行,一条线同步走下去
- 异步回调
- 其实就是耗时的我们都扔给异步去做,做好了再通知下我们做完了,我们拿到数据继续往下走
- 例如ajax,nodejs事件循环机制
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true); //第三个参数决定是否采用异步的方式
xhr.send(data);
xhr.onreadystatechange = function(){
if(xhr.readystate === 4 && xhr.status === 200){
///do something
}
}
2
3
4
5
6
7
8
var fs=require('fs');
function getData(callback){
fs.readFile('a.txt',function(err,data){
callback && callback(data);
})
}
getData(function(res){
console.log(res);
})
2
3
4
5
6
7
8
9
事件监听
也就是事件驱动模式,任务的执行不取决代码的顺序,而取决于某一个事件是否发生
常见事件监听函数:
- on
- bind
- listen
- addEventListener
- observe
以jQuery写法为例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="box">你好</div>
<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.js"></script>
<script>
function fn (){
console.log('div被点击了')
}
// 当div被点击了,就会调用fn函数,打印输出
$('#box').on('click',fn)
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 当同一个element元素绑定多个事件时,只有最后一个事件会被添加
el.onclick = function(){
//处理函数
}
// IE:attachEvent
el.attachEvent("onclick",fn);
// 标准addEventListener
el.addEvenListener("click",fn,false);
2
3
4
5
6
7
8
9
10
发布/订阅
- 发布订阅模式,又称观察者模式
ES6和ES6之后
- Promise
- async/await
TIP
Promise的出现让我们告别回调函数,写出更优雅的异步代码
Async/Await提供了一种使得异步代码看起来像同步代码的替代方法
Promise
- 三种状态:
- pending-进行中:Pending既不是Resolve也不是Rejected状态;可以理解为Promise对象实例创建时候的初始状态
- resolved-已完成:Resolve 可以理解为成功的状态
- rejected-已失败:Rejected 可以理解为失败的状态
var promise = new Promise((resolve, reject) => {
// 异步处理
// 成功调用resolve 往下传递参数 且只接受一个参数
// 失败调用reject 往下传递参数 且只接受一个参数
if (操作成功) {
resolve(value)
} else {
reject(error)
}
})
promise.then(function (value) {
// success
}, function (value) {
// failure
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Promise.then
对通过new 生成的promise对象为了设置其值在resolve(成功) / reject(失败) 时调用的回调函数,可以使用promise.then()实例方法
promise.then(onFulfilled, onRejected);
- resolve(成功) 时 调用onFulfilled 方法,reject(失败) 时 调用onRejected方法
- Promise.then 成功和失败时都可以使用
- Promise.then返回值还是一个Promise,我们可以链式编程一直点下去
Promise.catch
- 如果出现异常的情况下可以采用promise.then(undefined,onRejected) 这种方式,只指定onRejected回调函数即可,不过针对这种情况下我们有更好的选择是使用catch这个方法
promise.catch(onRejected);
- Promise.catch()方法是promise.then(undefined,onRejected)方法的一个别名,该方法用来注册当promise对象状态变为Rejected的回调函数
Promise.resolve('')
- 将现有的东西,转成一个promise对象
- 并且,这个对象是resolve状态(成功状态)
let p1 = Promise.resolve('小峰哥')
p1.then(res => {
console.log(res) // 成功打印:小峰哥
})
// 等价于
let p2 = new Promise(resolve => {
resolve('小峰哥')
})
p2.then(res=> {
console.log(res) // 成功打印:小峰哥
})
2
3
4
5
6
7
8
9
10
11
12
Promise.reject('')
- 现有的东西,转成一个reject对象
- 并且,这个对象是reject状态(失败状态)
Promise.all
- Promise.all可以接受一个元素为Promise对象的数组作为参数
- 当这个数组里面所有的promise对象都变为resolve时,该方法才会返回
- 如果有一个失败,是不会执行resolve的
var promise1 = new Promise(function(resolve){
setTimeout(function(){
resolve(1);
},3000);
});
var promise2 = new Promise(function(resolve){
setTimeout(function(){
resolve(2);
},1000);
});
Promise.all([promise1,promise2]).then(function(value){
console.log(value); // 打印[1,2]
});
2
3
4
5
6
7
8
9
10
11
12
13
14
注意:Promise.all方法中会按照数组的原先顺序将结果返回
比如我们需要发2个ajax请求时,不管他们的先后顺序,当这2个ajax请求都同时成功后,我们需要执行某些操作的情况下,这种情况非常适合
Promise.race
- Promise.all 在接收到的所有对象promise都变为FulFilled或者 Rejected状态之后才会继续后面的处理
- Promise.race的含义是只要有一个promise对象进入FulFilled或者Rejected状态的话,程序就会停止,且会继续后面的处理逻辑
Deferred
- Deferred 包含 Promise
- Deferred具备Promise的状态进行操作的特权方法
- Promise代表了一个对象,这个对象的状态现在还不确定,但是未来一个时间点它的状态要么变为正常值(FulFilled),要么变为异常值(Rejected)
- 而Deferred对象表示了一个处理还没有结束的这种事实,在它的处理结束的时候,可以通过Promise来取得处理结果
模块化
ES6之前
- Commonjs
- 服务端,require
- AMD
- require.js
- curl.js
- CMD
- sea,js
- UMD
TIP
ES6:统一了客户端和服务端规范
script引入模块
type='moduls
script添加type='moduls
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script type="module" src="./1.js"></script>
<!--
等同于defer:
浏览器对于带有type="module"的<script>,都是异步加载,不会造成堵塞浏览器
即等到整个页面渲染完,再执行模块脚本
等同于打开了<script>标签的defer属性
-->
<script type="module" src="./1.js" defer></script>
<!-- 也等价于 -->
<script type="module">
import {} from './1.js
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
TIP
模块化需要放到服务器环境中,file本地访问时无效的,localhost是可以的
export
- 导出模块
export let a = 1
export let b = 2
// 或者
export {
a,
b
}
2
3
4
5
6
7
8
export default
- export default 导出的不用花括号
export default 1
export let a = 2
export let b = 3
// 导入
import x,{a ,b} from './1.js'
2
3
4
5
6
import
- 导入模块
export let a = 1
export let b = 2
// 或者
export {
a,
b
}
// 导入
import {a , b} from './1.js'
// 或者
import * as OneModule from './1.js'
console.log(OneModule.a)
console.log(OneModule.b)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
特点(重要)
- import可以是相对路径,也可以是绝对路径
- import只会导入一次,无论你引入了多少次
- import 必须加上花括号
{}
,除非:使用 export default导出 - import有提升效果,import会自动提升到顶部,首先执行
- 到处去的内容,如果包里面的内容变化,那么导出的内容对应的数据也会变化的,不像command规范会缓存!
as 别名
let a = 111
export {
a as aaa
}
// 这里a无法使用了,要使用别名
import { aaa } from './1.js'
2
3
4
5
6
7
let a = 111
export {
a
}
// 给导出的a 起别名aaa,后面
import { a as aaa } from './1.js'
2
3
4
5
6
7
import()
- 动态引入,类似nodejs的require()
- 默认inport语法不能写在if里面的,import()可以
- 返回值:一个Promise()对象
- 优点:
- (动态引入)按需加载
- 可以写到if中
- 路径也可以写动态的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script type="module">
import('https://cdn.bootcss.com/jquery/3.4.0/jquery.js').then(res => {
console.log($)
})
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Promise.all([
import('./1.js'),
import('./2.js')
]),then(([mod1,mod2]) =>{
console.log(mod1)
console.log(mod2)
})
2
3
4
5
6
7
TIP
ES6模块化中默认使用严格模式 'use strict'
类(class)
- ES6中使用
class
来定义类,其实ES6的类,可以认为是一种语法糖
function Person (name) {
this.name = name
}
Person.prototype.hi = function () {
console.log('hi,man')
}
// 或者,合并对象
Object.assign(Person.prototype, {
hi: function () {
console.log('hi,man')
}
})
// 等价于ES6的写法
class Person {
constructor (name) {
this.name = name
}
hi () {
console.log('hi,man')
}
}
console.log(typeof Person) // function
console.log(Person === Person.prototype.constructor) // true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class表达式
const P = class Person {
constructor (name) {
this.name = name
}
hi () {
console.log('hi,man')
}
}
let p1 = new Person();
// 立即执行类,类似匿名函数自调用
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');
person.sayName(); // "张三"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 注意:这个类的名字是
Person
,但是只有在内部使用,外部还是使用P
constructor 方法
- constructor 方法 就是构造方法,而
this
关键字则代表实例对象 - 也就是说,ES5 的构造函数,对应 ES6 的类的构造方法
constructor
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法- 类必须调用new,而构造函数可以不调用new执行
TIP
可以直接对类进行new
操作,使用构造函数使用方法一样
使用类最直接的优点
代码层面
- 语法糖,代码美观
- 更偏向于大部分面向对象语言的写法
this问题
- 在ES6中的
class
使用类,它的this
使用会轻松非常多,不像我们es5中的this,使用起来非常麻烦 - 不太需要fn.call(this, 参数1,参数2) fn.apply(this, 数组) 以及 bind(this) 来修正this指向
- react 常在 constructor 中使用 bind 来修正 this 方法的指向,因为方法中可能用到
setState
方法,这个方法必须保证this的正确性
类的方法
- 在类中的方法都是,我们在ES5中的
prototype
对应的方法 - **在类的实例上面调用方法,其实就是调用原型上的方法 **
class Person {
constructor (name) {
this.name = name
}
//
hi () {
console.log('hi,man')
}
hello () {
console.log('hello')
}
}
// 等价于
Person.prototype.hi = function () {
console.log('hi,man')
}
Person.prototype.hello = function () {
console.log('hello')
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 类的内部所有定义的方法,都是不可枚举的(non-enumerable)
- 也就是不可以用
Object.keys
或者Object.getOwnPropertyNames
遍历(Person.prototype
)
变量提升
var
- 变量会提升,可以重复定义
- 显示声明为局部,不显示声明为全局变量
let
- 不支持变量提升,不可以重复定义
- 未初始化为undefined
const
- 不支持变量提升,不可以重复定义
- 一单定义无法修改
function 函数提升
- 必须是函数声明的形式,不能是函数表达式的形式
- 函数会首先被提升,然后才是变量
console.log(foo);
function foo(){
console.log("函数声明");
}
var foo = "变量";
console.log(foo)
/*
结果:
1. 第一个console是 整个foo 函数
2. 第二个console是 变量
分析:
* 变量和函数会被提升,然后函数在前
* 注意:变量的赋值是不会被提升的
*/
// 提升之后的代码块是:
function foo(){
console.log("函数声明");
}
var foo
console.log(foo); // 这里是第一个console,也就是这个时候foo 还是function,而不是变量,因为var foo 仅仅是声明了变量,这个时候打印foo 不会被认为是 var foo 对应的 undefined,而是 foo 函数
foo = "变量";
console.log(foo)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class
- ES6 不会把类的声明提升到代码头部
- 这种规定的原因与类的继承有关,必须保证子类在父类之后定义
let Foo = class {};
class Bar extends Foo {
}
// 代码不会报错,因为class不会被提升
// 如果存在class的提升,上面代码就会报错,因为class会被提升到代码头部,而let命令是不提升的,所以导致Bar继承Foo的时候,Foo还没有定义
2
3
4
5
取值函数(getter)和存值函数(setter)
class Person {
constructor (name) {
this.name = name
}
set age (val) {
console.log('set', val)
}
get age () {
console.log('get')
return ‘456’
}
hi () {
console.log('hi,man')
}
}
let p = new Person()
p.age = 123 // set 123
console.log(p.age) // 先输出get 在输出 ---456
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
TIP
- set 必须有参数,get不允许有参数
- get的返回值就是我们获取的值
属性表达式
类似属性名可以用表达式
let a = 'fn'
class Person {
constructor (name) {
this.name = name
}
// 这里属性表达式,还是挺常用的
[a] () {
console.log('我是fn')
}
}
let p = new Person()
p.fn() // 我是fn
p[a]() // 我是fn
p.a() // 报错,p.a is not a function
2
3
4
5
6
7
8
9
10
11
12
13
14
15
静态方法
类身上的方法,类可以直接调用,实例不可以调用,使用static
修饰
class Person {
constructor (name) {
}
static fn () {
console.log('static方法')
}
}
let p = new Person()
Person.fn() // console.log('static方法')
p.fn() // 报错,是不允许的-p.fn is not a function
2
3
4
5
6
7
8
9
10
11
继承 extends
类可以通过extends
关键字来实现继承
class Person {
constructor (name) {
this.name = name || '无名氏'
}
hi () {
console.log(this.name, '你好')
}
// 静态方法
static say () {
console.log('say')
}
}
class Student extends Person {
// 注意这里是没有调用 constructor 方法的
}
let stu1 = new Student('张三')
Student.say() // say 静态方法被继承了
stu1.hi() // 张三 你好 实例方法也被继承了
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 这里的stu1 继承 Person 类,可以调用Person方法
子类添加 constructor 方法
- 子类必须在constructor 方法中调用
super
方法,否则就会报错 - 原因:
- 子类自己的this对象,必须先通过弗雷的构造函数塑造,得到与父类同样的实例属性和方法,然后再进行自己的操作
- 如果不调用
super
方法,子类就得不到自己的this
class Student extends Person {
constructor (name) {
// 这里关键 super
super(name)
}
hello () {
console.log('hello')
}
}
2
3
4
5
6
7
8
9
TIP
子类会默认添加constructor方法,即使没有显式定义,任何一个子类也都是有constructor方法的
Object.getPrototypeOf()
用来判断一个类是否继承了另一个类,原因是这个方法可以从子类上获取父类
Object.getPrototypeOf(Student) === Person // true
super 关键字
- 作为函数使用
- 作为对象使用
super 函数
super作为函数调用时,代表父类的构造函数,子类创建的时候要求调用父类的构造函数
class Person {
constructor (name) {
this.name = name || '无名氏'
}
// 静态方法
static say () {
console.log('say')
}
}
class Student extends Person {
constructor (name) {
// 这里关键 super
super(name)
}
}
let stu1 = new Student('张三')
Student.say()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 注意:
- 虽然这里的super代表父类构造函数,但是这里的this代表的却是子类
- 也就是这里的super(),相当于
Person.prototype.constructor.call(this)
- 修改this的指向
TIP
super()只能用在子类的构造函数之中,用在其他地方就会报错
super 对象
super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class Person {
constructor (name) {
this.name = name || '无名氏'
}
fn1 () {
console.log('fn1')
}
static fn2 () {
console.log('fn2')
}
}
class Student extends Person {
constructor (name) {
super(name)
}
// 普通方法
fn3 () {
super.fn1()
super.fn2()
}
// 静态方法
static fn4 () {
super.fn2()
super.fn1()
}
}
let stu1 = new Student('张三')
stu1.fn3() // 先输出fn1,然后报错,也就是无法调用fn2,无法调用静态方法
Student.fn4() // 先输出fn2,然后报错,也就是无法调用fn1,无法调用普通方法
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
原生构造函数的继承
原生构造函数
- Boolean()
- Number()
- String()
- Array()
- Date()
- Function()
- RegExp()
- Error()
- Object()
ES5是无法实现原生构造函数基础的,即使我们改变this指向,也会被忽略
ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承
Symbol
- ES6引入了一种新的原始数据类型
- 其余数据类型是:
- undefined
- null
- 布尔值(Boolean)
- 字符串(String)
- 数值(Number)
- 对象(Object)
- 特点:
- 独一无二,同样创建方法创建出来的Symbol也是不相等的
- typeof 结果是
Symbol
Symbol创建
- 调用构造函数
Symbol()
即可创建 - 注意不允许使用new,因为他是原始数据类型,不是对象
let sym = Symbol('symb')
console.log(sym) // Symbol(symb)
let sym1 = Symbol('symb1')
console.log(sym1) // Symbol(symb1)
let s1 = Symbol('s')
let s2 = Symbol('s')
console.log(s1==s2) // false,不相等,即使参数一样
2
3
4
5
6
7
8
9
- symbol 函数的参数是一个字符串,用于添加对Symbol实例进行描述区分
- 如果参数是对象,那么就会调用该对象的
toString
方法,先转化为字符串,然后生成Symbol的值
Symbol常用场景
- 作为对象的属性,防止对象属性名重复,会被覆盖掉
- 单利模式创建的时候,推荐使用
Symbol常见坑
- Symol 作为属性名,遍历对象的时候是不会出现的:
- for in
- for of
- Object.keys()
- Object.getOwnPropertyNames()
- JSON.stringify()
Symbol常见方法
Symbol.for()
- 接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值
- 如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局
- 区别:
Symbol.for()
与Symbol()
这两种写法,都会生成新的 Symbol- 它们的区别是,前者会被登记在全局环境中供搜索,后者不会。
Symbol.for()
不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key
是否已经存在,如果不存在才会新建一个值 - 比如,如果你调用
Symbol.for("cat")
30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")
30 次,会返回 30 个不同的 Symbol 值
Symbol.keyFor()
Symbol.keyFor()
方法返回一个已登记的 Symbol 类型值的key
generator 函数
- Generator 函数是 ES6 提供的一种异步编程解决方案
语法形式
function * fnName () {
yield // 只能和generator 配合使用
}
2
3
简单示例
// 定义函数
function * gen () {
yield 'nihao'
yield 'zhangsan'
return 'gen-fn'
}
// 调用
let g1 = gen()
console.log(g1) // 直接执行就是一个对象
console.log(g1.next()) // { value: 'nihao', done: false }
console.log(g1.next()) // { value: 'zhangsan', done: false
console.log(g1.next()) // { value: 'gen-fn', done: true }
2
3
4
5
6
7
8
9
10
11
12
13