目录
+ ECMAScript 发展过程中的一个版本
=> 官方: ES2015
=> 社区: ES6
+ ES6 和之后所有的内容在 IE 低版本不兼容
1. ES6 - 定义变量
ES2015(ES6) 新增加了两个重要的 JavaScript 关键字 : let 和 const 。
let 声明的变量只在 let 命令所在的代码块内有效 ,
const 声明一个只读的常量,一旦声明,常量的值就不能改变 。
1. let : 变量
2. const : 常量
1. let / const 和 var 的区别
1-1. 预解析
=> 在预解析的过程中 var 定义的变量会被预解析, 可以先使用后定义
=> let / const 不会进行预解析, 必须先定义后使用// 1-1. 预解析 console.log(num) // undefined var num = 100 console.log(num) // 100
// let 定义变量,不会进行预解析 console.log(num) // 会报错 let num = 100 console.log(num) // const 定义变量,不会进行预解析 console.log(num) // 会报错 const num = 100 console.log(num)
1-2. 重复声明(变量重名)
=> 使用 var 可以定义两个一模一样的变量, 只是第二次定义没有意义, 赋值有意义
=> let / const 不允许在同一个作用域下, 定义重名变量// 1-2. 重复声明 var n = 100 var n = 200 console.log(n) // 200
let n2 = 200 let n2 = 300 // 报错 => Identifier 'n2' has already been declared const n3 = 300 const n3 = 400 // 报错 => Identifier 'n3' has already been declared
1-3. 块级作用域
=> var 没有块级作用域
=> let / const 有块级作用域
=> 块级作用域: 任何一个可以书写代码段的 { } 都会限制变量的使用范围// 1-3. 块级作用域 if (true) { var num = 100 console.log(num) // 100 } console.log(num) // 100
if (true) { let num = 100 console.log(num) // 100 } console.log(num) // 报错 => num is not defined if (true) { const num = 200 console.log(num) } console.log(num) // 报错 => num is not defined
2. let 和 const 的区别
2-1. 声明时赋值
=> let 在定义变量的时候, 可以不赋值
=> const 在定义变量的时候, 必须赋值// 2-1. 赋值问题 let num console.log(num) // undefined num = 200 console.log(num) // 200 // 会报错, 初始化的时候必须赋值 ,不能定义常量不赋值 const n // 报错 => Missing initializer in const declaration
2-2. 值的修改
=> let 定义的变量可以任意修改值内容
=> const 定义的值, 在定义时赋值, 一旦赋值不允许修改// 2-2. 值的修改 let n = 100 console.log(n) // 100 n = 'hello world' console.log(n) // hello world const str = '我是定义时就写好的内容' console.log(str) // 我是定义时就写好的内容 // 当你试图修改一个 const 定义的常量 // 直接会报错, 因为 const 声明的常量不允许修改 str = 'hello world'
for 循环计数器很适合用 let
for (var i = 0; i < 10; i++) { setTimeout(function(){ console.log(i); }) } // 输出十个 10 for (let j = 0; j < 10; j++) { setTimeout(function(){ console.log(j); }) } // 输出 0123456789
变量 i 是用 var 声明的,在全局范围内有效,所以全局中只有一个变量 i, 每次循环时,setTimeout 定时器里面的 i 指的是全局变量 i ,而循环里的十个 setTimeout 是在循环结束后才执行,所以此时的 i 都是 10。
变量 j 是用 let 声明的,当前的 j 只在本轮循环中有效,每次循环的 j 其实都是一个新的变量,所以 setTimeout 定时器里面的 j 其实是不同的变量,即最后输出 0123456789。(若每次循环的变量 j 都是重新声明的,如何知道前一个循环的值?这是因为 JavaScript 引擎内部会记住前一个循环的值)。
注意要点 :
const 如何做到变量在声明初始化之后不允许改变的?其实 const 其实保证的不是变量的值不变,而是保证变量指向的 内存地址 所保存的数据不允许改动。此时,你可能已经想到,简单数据类型和复杂数据类型保存值的方式是不同的。是的,对于简单数据类型(数值 number、字符串 string 、布尔值 boolean),值就保存在变量指向的那个内存地址,因此 const 声明的简单数据类型变量等同于常量。而复杂数据类型(对象 Object,数组 Array,函数 Function),变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是固定的,至于指针指向的数据结构变不变就无法控制了,所以使用 const 声明复杂数据类型对象时要慎重。
2. ES6 - 模板字符串
+ ES6 新增了一种定义字符串的方式 => 模板字符串
模板字符串(template string)是增强版的字符串
+ 使用 反引号(` `) 来进行字符串的定义
+ 和 单引号(' ') 或者 双引号(" ") 定义的字符串没有区别, 使用上是一样的
+ 只是当你使用 反引号(` `) 定义的时候, 会有特殊的能力
模板字符串的特点:
1. 可以换行书写字符串
2. 可以直接在字符串内拼接解析变量
=> 当你需要解析变量的时候, 书写 ${ 变量 }3. 可以调用函数
+ 注意:
=> ${ } 外面的空格是真实在字符串内的空格
=> ${ } 里面的空格是代码的空格, 和字符串没有关系const s1 = 'hello world' const s2 = "hello world" const s3 = `hello world` console.log(s1, typeof s1, s1.length, s1.slice(0, 5)) console.log(s2, typeof s2, s2.length, s2.slice(0, 5)) console.log(s3, typeof s3, s3.length, s3.slice(0, 5)) console.log(s1 === s2) // true
// 特点1: 换行书写字符串 let str = ` hello world 你好 世界 ` console.log(str) // 特点2: 解析变量 let age = 18 let arr = [ 100, 200, 300 ] // 普通单引号( ' ' ) let s1 = '你好 我今年 ${ age } 岁了' // ${ } 可以书写一个简单的表达式 let s2 = `你好 我今年 ${ arr.length ? arr[arr.length - 1] : 18 } 岁了` console.log(s1) console.log(s2)
// 特点 3 : 可以调用函数 function fn(a) { console.log(a); // (3) ["hello ", " world ", "", raw: Array(3)] } let num = 100 fn`hello ${ num } world ${ num }` // ${} 会把字符串切割成数组, 并当成第一个参数
function fn(a, b, c) { console.log(a); // (3) ["hello ", " world ", "", raw: Array(3)] console.log(b); // 100 console.log(c); // 200 } let num1 = 100 let num2 = 200 fn`hello ${ num1 } world ${ num2 }` // num1 就是该函数的第二个参数 // num2 就是该函数的第三个参数
3. ES6 - 箭头函数
箭头函数提供了一种更加简洁的函数书写方式。
+ ES6 语法中定义函数的一种方式
+ 只能用来定义函数表达式(赋值式函数/匿名函数)
=> 当你把函数当做一个值赋值给另一个内容的时候, 叫做 函数表达式
=> var xxx = function () {}
=> var obj = { xxx: function () {} }
=> xxx.onclick = function () {}
=> xxx.addEventListener('click', function () {})
=> xxx.forEach(function () {})
=> setTimeout(function () {}, 1000)
=> setInterval(function () {}, 1000)
=> ...
+ 注意: 声明式函数不行
=> function fn( ) { }
+ 箭头函数的语法: ( ) => { }
-> ( ) 书写函数形参的位置
-> => 是箭头函数的标志
-> { } 函数的函数体 , 是书写代码段的位置// 写一个箭头函数 const fn = function () { console.log('我是一个函数') } const fun = () => { console.log('我也是一个函数') } fn() fun()
箭头函数 和 函数表达式的区别
1. 箭头函数内没有 arguments
=> 函数内一个伪数组, 是所有实参的集合
2. 箭头函数内没有 this
=> 官方: 箭头函数内的 this 是上下文(context) 的 this
=> 私人: 箭头函数外面的函数 this 是谁, 箭头函数内的 this 就是谁
箭头函数的特点:(不强制要求)
1. 可以省略小括号不写
=> 当你的形参有且只有一个的时候, 可以不写小括号
=> 如果你的形参没有或者两个及以上, 必须写小括号// 1. 省略小括号 //没有形参, 小括号必须写 let fn1 = () => { console.log('我是 fn1 函数, 我没有形参') } fn1() // 一个形参, 可以不写小括号 let fn2 = a => { console.log('我是 fn2 函数, 有一个参数', a) } fn2(10) // 两个形参, 必须写小括号 let fn3 = (a, b) => { console.log('我是 fn3 函数, 有两个参数', a, b) } fn3(100, 200)
2. 可以省略大括号不写
当你的代码有且只有一句话的时候, 可以省略大括号不写, 并且会自动返回这一句话的结果
否则, 必须写大括号// 2. 省略大括号 // 这是一个箭头函数, 函数内只有一句代码, 是 a + b // 这个函数的返回值, 就是 a + b 的结果 let fn1 = (a, b) => a + b let res = fn1(10, 20) console.log(res) // 打印 => 30
// 简写过程 const arr = [ 1, 2, 3, 4, 5, 6, 7 ] // var res = arr.filter(function (item) { return item % 2 }) // var res = arr.filter((item) => { return item % 2 }) // var res = arr.filter(item => { return item % 2 }) const res = arr.filter(item => item % 2) console.log(res) // 打印 => (4) [1, 3, 5, 7]
3. 箭头函数内没有 arguments
=> 箭头函数内天生不带有 arguments
=> 没有所有实参的集合// 3. 没有 arguments const fn1 = function () { console.log(arguments) } fn1(10, 20, 30) const fn2 = () => { console.log(arguments) } fn2(10, 20, 30)
4. 箭头函数内没有 this
=> 官方: 外部作用域的 this
=> 私人: 书写在箭头函数的外面那个函数 this 是谁, 箭头函数内的 this 就是谁
注意 : 不可以作为构造函数,也就是不能使用 new 命令,否则会报错// 4. 箭头函数内没有 this const obj = { name: '我是 obj 对象', f: function () { console.log('f : ', this) }, f2: () => { // 这里没有 this, 用的是 外部的 this console.log('f2 : ', this) } } obj.f() // 按说看调用方式, f2 内的 this 应该是 obj // 因为 f2 是一个箭头函数, 所以该函数内没有 this // this 使用的是上下文的 this obj.f2()
4. ES6 - 函数参数默认值
+ 给函数的形参设置一个默认值, 当你没有传递实参的时候, 使用
+ 书写: 直接在书写形参的时候, 以 赋值符号( = ) 给形参赋值设置默认值就可以了
+ 任何函数都可以使用
+ 箭头函数也能设置参数默认值 :
=> 注意: 如果你给箭头函数设置参数默认值, 那么不管多少个形参
=> 都需要书写小括号// 给形参 a 设置了默认值为 10 // 给形参 b 设置了默认值为 20 function fn(a = 10, b = 20) { console.log('fn 函数内的打印') console.log('a : ', a) console.log('b : ', b) console.log('---------------------') }
// 箭头函数也可以设置默认值 const fn = (a = 10, b = 20) => { console.log('fn 函数内的打印') console.log('a : ', a) console.log('b : ', b) console.log('---------------------') } // 第一次调用 // 给 两个形参 赋值了, 那么就不使用默认值了 fn(100, 200) // 第二次调用 // 没有给 b 赋值, 那么 b 就会使用 20 这个默认值 fn(1000) // 第三次调用 // a 和 b 都没有实参进行赋值, 都会使用 默认值 fn()
// 范围内的随机数 // 定义函数, 两个数字分别默认值设置成 0 和 255 const randomNum = (a = 255, b = 0) => Math.floor(Math.random() * (Math.abs(a - b) + 1)) + Math.min(a, b) console.log(randomNum(10, 100))
5. ES6 - 解构赋值
解构赋值概述 :
解构赋值是对赋值运算符的扩展。
它是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
解构模型 :
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为 解 构(Destructuring)。
在解构中,有下面两部分参与:
解构的源,解构赋值表达式的右边部分;
解构目标,解构赋值表达式的左边部分;
+ 目的: 快速从 对象 或者 数组 中获取一些数据
+ 分成两类
1. 解构数组
2. 解构对象const arr = [10, 20, 30] // 需求: 单独定义三个变量获取三个数据 // 原先的方式, 不够方便 const n1 = arr[0] const n2 = arr[1] const n3 = arr[2]
1. 解构数组
+ 快速从数组中获取一些数据
+ 注意: 使用 [ ] 解构数组
+ 语法: const [ 变量1, 变量2, 变量3, ... ] = 数组
=> 按照数组的索引依次给 解构 内的 变量进行赋值// 1. 使用解构数组来获取 // n1 就获取 arr 中 [0] 位置的数据 // n2 就获取 arr 中 [1] 位置的数据 // n3 就获取 arr 中 [2] 位置的数据 const [ n1, n2, n3 ] = arr console.log(n1, n2, n3) // 打印 => 100 200 300
+ 扩展: 多维数组的解构
=> 原先数组长什么样, 解构就长什么样
-> 数组怎么写, 解构怎么写
-> 把数据替换成变量// 扩展 : 多维数组的解构 const arr = [1, 2, [3, 4, [5, 6, [7, 8, [9, [10]]]]]] console.log(arr) // (3) [1, 2, Array(3)] // 需求: 定义变量拿到 9 这个数据 const a = arr[2][2][2][2][0] console.log(a) // 9
// 解构: const [a, b, [c, d, [e, f, [g, h, [i, [j]]]]]] = arr console.log(i) // 9
// 2. 解构对象 const obj = { name: 'Jack', age: 18, gender: '男' } // 以前 const name = obj.name const age = obj.age const gender = obj.gender console.log(name, age, gender) // Jack 18 男
2. 解构对象
+ 快速从对象内获取一些数据
+ 注意: 解构对象使用 { }
+ 语法: const { 键名1, 键名2, 键名3, ... } = 对象
=> 按照键名, 依次定义变量从对象中获取指定成员// 解构 const { name, age, gender } = obj console.log(name, age, gender) // Jack 18 男
=> 解构的时候起一个 别名
+ 别名: const { 键名: 别名, 键名2: 别名 } = 对象
-> 注意: 当你起了别名以后, 原先的键名不能在当做变量名使用了, 需要使用这个别名// 2-2. 解构的时候起一个别名 const obj = { name: 'Jack', age: 18, gender: '男' } // const { name } = obj 等价于 const name = obj.name // const { name: n } = obj 等价于 const n = obj.name const { name: n, gender, age: a } = obj console.log(n, gender, a) // Jack 男 18
+ 扩展: 解构多维对象
=> 原先对象长什么样, 解构就长什么样, 数据替换成变量// 扩展: 解构多维对象 const obj = { name: 'Jack', age: 18, info: { weight: 180, height: 180, data: { desc: '这个人很懒, 什么都没有留下', hobby: [ '吃饭', '睡觉' ] } } } // 我想拿到睡觉 // 解构 const { name: n, age, info: { weight: w, height: h, data: { desc, hobby: [ a, b ] } } } = obj console.log(h) // 180 console.log(b) // 睡觉
面试案例 :
// 一号坑 :
const obj = { name: 'Jack', age: 18, gender: '男' } const { age: a } = obj // 等价于执行 const a = obj.age console.log(a) // 18 console.log(age) // 报错打印 => age is not defined // 当你给一个数据起了别名之后, 原先这个键名就不能够再当做变量名使用了, 用的就是别名了
const obj = { name: 'Jack', age: 18, gender: '男' } const { name: n } = obj // 等价于 const n = obj.name console.log(n) // Jack console.log(name)// 不报错 => 空字符串( ' ' ), 因为 Window 中天生自带一个 name 属性
//唯独 name 可以单独使用,因为是 Window 天生自带了这么一个属性,且被锁定为 string 类型 name = 18 console.log(typeof name) // 打印 => string
=>
// 二号坑 :
// const 定义的常量不能被修改 // obj 接受的值是一个 对象数据类型 的地址 // 什么情况下才叫做把 obj 的值修改, 更换地址 const obj = { name: 'Jack' } //并没有修改 obj 的值, 而是把 obj 内空间内的值修改了 obj.name = 'Rose' console.log(obj.name) // Rose //( 当你重新 const obj = { ...... } 时 ) //会报错 : Identifier 'obj' has already been declared( 标识符'obj'已经被声明 )
// 三号坑 :
for (var i = 0; i < 3; i++) { // 异步 setTimeout(() => { console.log(i) // 打印 => 三个 3 }, 0) } /* 全局定义变量 i 开始循环 i === 0 => 定义一个定时器, 定时器处理函数执行吗 ? => 不执行, 等同步代码都执行完才执行 => 向队列内放了一个函数 () => { console.log(i) } i === 1 => 定义一个定时器 => 向队列内放了一个函数 () => { console.log(i) } i === 2 => 定义一个定时器 => 向队列内放了一个函数 () => { console.log(i) } i === 3 循环结束 此时所有同步代码执行完毕, 才会开始执行异步代码 => 直接执行队列内的三个函数 => 在控制台打印三次 i */
// 三号坑 挖深 :
// 块级作用域: 任何一个可以书写代码段的 { } 都会限制变量的使用范围 for (let i = 0; i < 3; i++) { // 异步 setTimeout(() => { console.log(i) // 打印 => 0 1 2 }, 0) } /* 打开页面 循环开始 => 注意 : let 定义的变量会被 { } 锁住 { i === 0 准备了一个定时器处理函数, 在队列内, () => console.log(i) } { i === 1 准备了一个定时器处理函数, 在队列内, () => console.log(i) } { i === 2 准备了一个定时器处理函数, 在队列内, () => console.log(i) } { i === 3 循环结束 } 所有同步代码执行完毕, 开始执行异步代码 => 打印 0 1 2 */
// 三号坑 再 挖深 :
for (let i = 0; i < 3; i++) { // 异步 setTimeout(() => { console.log(i) // 报错之后打印 => 0 1 2 }, 0) } console.log(i) // 报错 => i is not defined // 依旧会继续打印出 0 1 2 // 因为定时器的设置, 是在报错以前设置的
不是说报错就会中断程序的执行么 , 可为啥后面的代码还执行打印出来结果了呢 ?
因为 setTimeout 是你异步队列里面的代码 , 是在你报错之前就已经准备好要执行的代码
它只是从报错以后的代码不执行 , 但这一句是报错之前的代码 ,所以依旧会打印出 0 1 2
是因为定时器的设置是在报错以前设置的 , 浏览器会终结到报错之前 ,
把你所有准备好的代码都执行了
比如 : 我们可以把它想象成你指挥一个人 , 当这个人他做错了事 , 他就不再继续往下做事了 ,
在这里你跟他说 : 你去 定一个 闹钟 , 1 秒钟以后 响一下 , 再设置一个 , 1分钟后响 ,
然后这个人去接热水时 , 不小心把手给烫了 , 所以就没办法再办事了 ,
但是他已经把 闹钟 都设置好了 ,到时间就会响 , 所以跟他 能不能办事了就没有任何关系了
6. ES6 - 扩展运算符
+ 在 ES6 语法里面多了一个运算符
+ 展开合并运算符
+ 有两个意义
1. 展开 => 展开运算符 (...)
2. 合并 => 合并运算符 (...)
+ ...
=> 主要是操作 数组 和 对象 的运算符号
1. 展开运算符
=> 可以 展开对象, 或者 展开数组
=> 多用于数组
=> 当你使用在 数组 或者 对象 或者 函数实参位置 的时候叫做 展开运算
=> 如果是展开对象, 就是去掉 对象 的 { }
=> 如果是展开数组, 就是去掉 数组 的 [ ]// 1. 展开运算符 const arr = [ 100, 200, 300 ] // 如果我想在控制台打印 100 200 300 console.log(arr) // (3) [100, 200, 300] console.log(100, 200, 300)// 100 200 300 console.log(...arr) // 100 200 300 const arr2 = [...arr, 400, 500] console.log(arr2) // (5) [100, 200, 300, 400, 500] const res = Math.max(...arr) console.log(res) // 300
// 展开对象 const o1 = { name: 'Jack', age: 18 } const o2 = { gender: '男', ...o1 } const o3 = { ...o2, score: 200 } console.log(o1, o2, o3)
2. 合并运算符
=> 用于合并多个数据使用
=> 当你使用在 解构数组 或者 函数的形参位置 的时候叫做 合并运算
=> 当这个符号书写在 函数 的 形参 位置的时候, 叫做 合并运算符
=> 从当前形参位置开始获取实参, 直到末尾
=> 注意: 合并运算符一定要写在最后一位// 2. 合并运算符 const arr = [ 100, 200, 300 ] // 在解构中使用合并运算符 // 把 arr 中 [0] 赋值给 a // 把 arr 中 [1] 开始到末尾的所有内容都赋值给 b, 以一个新数组的形式出现 // 注意: 合并运算符必须写在最后一个的位置 const [ a, ...b ] = arr console.log(a) // 100 console.log(b) // (2) [200, 300] // 在函数形参位置使用 // 把第一个实参赋值给 a // 剩下的所有实参给到 b, 以一个新数组的形式出现 // 注意: 合并运算符必须写在最后一个的位置 function fn(a, ...b) { console.log(a) // 100 console.log(b) // (2) [200, 300] } fn(100, 200, 300)
// 箭头函数有一个特点, 没有 arguments // 我们可以使用 ... 合并出一个 arguments const fn = (...arg) => { console.log(arg) } fn(10, 20, 30) // (3) [10, 20, 30]
7. ES6 - 对象简写语法
ES6 - 对象简写语法(不强制要求)
1. 当 key 和 value 一模一样, 并且 value 是一个变量的时候
=> 可以省略 value 和 冒号( : ) 不写
2. 当某一个 key 的值是一个函数, 并且不是箭头函数的时候
=> 可以直接省略 function 关键字和 冒号( : ) 不写// 1. key 和 value 一样 const day = 12 const hours = 23 const minutes = 33 const seconds = 25 const obj = { day, // 等价于你写了 day: day hours, minutes, seconds, // key 和 value 虽然一模一样 // 但是 value 不是一个变量 data: 'data' } console.log(obj)
// 2. 函数简写 const obj = { name: '我是 obj 对象', // f1 是一个函数, 但是不是箭头函数, 可以简写 f1: function () { console.log(arguments) }, // f2 虽然是一个函数, 但是是一个箭头函数, 不能简写了 f2: () => { console.log(arguments) }, // f3 是一个函数, 并且不是箭头函数 f3 () { console.log(arguments) }, f4 () { // ... } } obj.f1(1, 2, 3) obj.f3(10, 20, 30)
8. ES6 - 模块化开发
开发的历史演变 :
=> 最早: 一个 js 文件
-> 每一个 html 文件对应一个 js 文件
=> 后来: 把一个项目内部的重复功能提取出来
-> 写成一个单独的 js 文件
=> 后再:
-> 决定按照功能拆分出一个一个的文件
-> a.js : 专门做顶部导航栏各种功能
-> b.js : 专门做二级菜单
-> c.js : 专门做搜索引擎
-> d.js : 左侧边栏
-> e.js : 轮播图
-> 最后在每一个 页面 准备有一个整合的 js 文件
+ 就是专门用来组合这个页面使用了多少个 js 文件模块
-> 此时, 我们管每一个 js 文件叫做一个 模块
+ 页面的完整功能, 就是由一个一个的模块来完成的
-> 叫做 模块化 开发
模块化开发的规则 :
1. 如果你想使用 ES6 的模块化开发语法
=> 页面必须在服务器上打开
=> 本地打开不行
=> VS Code 下载插件, Live Server ( 会帮你配置一个虚拟服务器 )
=> 打开页面的时候, 鼠标右键 open with live server
-> 快捷键, alt + l, 再按一次 alt + o
2. 当你使用了 模块化语法以后
=> 每一个 js 文件, 都是一个独立的 文件作用域
=> 该文件内的所有变量和方法, 都只能在这个文件内使用
=> 其他文件不能使用
=> 如果想使用, 需要导出
3. 每一个 js 文件, 也不能使用任何其他 js 文件内部的变量
=> 如果想使用
=> 那么你需要导入该文件
语法: 导出和导入
+ 导出语法 :
export default { 你要导出的内容 }
+ 导入语法 :
import 变量 from 'js 文件'<!-- 只需要引入一个 index.js 文件 --> <!-- 因为 index.js 文件内使用着 其他需要用到的模块 --> <!-- 如果你页面引入的是一个使用了 ES6 模块化语法的 js 文件 --> <!-- 必须在服务器上打开, 并且给 script 标签添加一个 type="module" 的属性 --> <script src="./js/index.js" type="module"></script>
9. ES6 - 类语法
+ 类 就是专门用来创建对象的内容
+ 解释: ES6 书写构造函数的方式
+ 私人: 就是 构造函数语法:
+ class 类名 { }
=> class: 定义类的关键字
=> 类名: 该类的名字
=> { }: 该类里面包含的所有内容
+ 在类{ }里面书写的内容
1. construcor ( ) { }
=> 等价于 ES5 的构造函数体
2. 构造函数原型上的方法
=> 直接在 constructor 的后面书写
=> 方法名 ( ) { }
=> 表示是添加在原型上的方法
=> 等价于 构造函数.prototype.方法名 = function ( ) { }
3. 类 的 静态方法和属性
=> static 方法名 ( ) { }
=> static 变量 = 值
注意:
+ 因为 构造函数 的本质是 函数, 所以可以当做普通函数调用
+ 但是 类 的本质是 类, 不是函数, 所以不能当做普通函数调用// ES5 书写构造函数 function Person() { this.age = 18 } Person.prototype.sayHi = function () { console.log('hello world') } // hello world const p = new Person() p.sayHi() console.log(p) // Person {age: 18} // 构造函数当做普通函数调用 Person() // ES6 类 的书写 class Student { constructor () { // 这个位置就等价于你 ES5 的构造函数体 this.age = 20 } // 直接书写原型上的方法 sayHi () { console.log('hello student') }// hello student } const s = new Student() s.sayHi() console.log(s) // Student {age: 20} // 如果不和 new 关键字连用, 直接把 类 当做普通函数执行, 会报错 // 类的使用 : 因为 类 的本质不是 函数了 // 使用的时候必须和 new 关键字连用 Student()
如何区分静态方法和原型方法
+ 在书写上
=> 如果你想写一个方法将来给实例使用, 那么就写成原型方法
=> 如果你想写一个方法将来给类自己使用, 那么就写成静态方法
=> 如果你想写成原型方法, 那么就直接写 ( 方法名 ( ) { } )
=> 如果你想写成静态方法, 那么就直接写 ( static 方法名 ( ) { } )// ES6 的类 class Person { // "构造函数体" constructor (name, age) { this.name = name this.age = age } // 原型上的方法 sayHi // 添加在 Person 的 prototype 上, 专门用来给该 类 的 所有实例 使用的方法 sayHi () { console.log('hello world') } sayHi2 () {} sayHi3 () {} // 添加静态方法和属性 // 把 Person 当做一个对象来看待, 添加在自己身上的方法 // 不是为了给实例使用的, 实例也不能使用 // 是为了给自己使用的 static fn1 () { console.log('我是 Person 类的一个静态方法') } static a = 100 }
+ 在使用上
=> 如果你写的就是 ( 方法名 ( ) { } )
=> 那么将来你使用的时候, 要依赖这个类的 实例对象 去调用
-> const p1 = new Person( )
-> p1.方法名( )
=> 获取在方法内以 this 的形式来调用
-> 方法名 ( ) { this.xxx }
=> 如果你写的是 ( static 方法名 ( ) { } )
=> 那么将来使用的时候要依赖 类名 去调用
-> 类名.方法名( )
// ES5 的构造函数 function Person(name, age) { this.name = name this.age = age } // 原型上的方法 // 由构造函数来添加, 专门给 实例 使用的, 不是给函数自己本身使用的 Person.prototype.sayHi = function () { console.log('hello world') } // 因为 Person 是一个构造函数, 可以创建对象 // 使用的时候需要和 new 关键字连用 const p1 = new Person('张三', 18) console.log(p1) // Person {name: "张三", age: 18} // 因为 Person 的本质就是一个函数, 所以也可以不和 new 关键字连用 // 只是没有了自动创建对象的能力, 但是本身没有错 // 把函数体内的内容添加在了 Window 身上( this 指向 Window ) const p2 = Person('李四', 20) console.log(p2) // undefined // 又因为 Person 是一个函数的同时, 也是一个对象 // 可以当做对象来存储一些键值对 // 其实就是 Person 的函数身份不冲突 // 当你把 Person 当做一个对象, 添加一些成员的时候 // 注意: 不是给实例使用的, 实例也不能使用, 给函数自己本身使用的 // 我们管这种给函数自己使用的方法和属性叫做 该函数的 静态属性和方法 // ( 就是把函数当成一个对象 , 往自己身上添加属性和方法 , 供自己使用的 ) Person.a = 100 Person.sayHi2 = function () { console.log('Person 身上自己的方法') } console.dir(Person) // ƒ Person(name, age) // 等价于 const obj = { name: 'Jack', sayHi2: function () { } }
ES5 的时候, 你为什么要写构造函数
=> 看中了可以创建对象的能力
=> 为了高度封装, 一次书写这个功能
=> 以后在完成的时候方便很多
=> 一般构造函数不会不和 new 连用, 不会把他当做一个普通函数来使用ES6 的时候, 进行一下进化
=> 把他当做普通函数调用的能力给去掉了
=> 去掉以后, 就不能再叫做 构造函数 了
=> 给自己起了一个新的名字, 叫做 类
=> 因为进化了, 总要有一些好处, 有一些变化原先写成
function Person( ) {
// 构造函数体
}
现在写成
class Person {
constructor ( ) {
// "构造函数体"
}
}原型上的方法原先写成
Perosn.prototype.a = function ( ) { }
原型上的方法现在写成
class Person {
conctructor ( ) { ... }a ( ) { }
b ( ) { }
}静态方法原先写成
Person.c = function ( ) { }
Person.d = function ( ) { }
静态方法现在写成
class Person {
constructor ( ) { ... }a ( ) { }
static c ( ) { }
static d ( ) { }
}
整合
ES5 :
function Person( ) { }Person.prototype.a = function ( ) { }
Person.c = function ( ) { }
ES6 :
class Person {
constructor ( ) { }a ( ) { }
static c ( ) { }
}