一、块级作用域绑定
回顾:使用var关键字定义变量
定义 = 声明 + 赋值;
1. 可以一次定义多个变量
2. 定义时可以只声明不赋值
3. 定义之后可以随时修改变量的值
4. 变量声明会被提升
5. 可重复定义变量
6. 全局定义的变量会被作为全局对象(global/window)的属性
7. 在代码块中使用 var 关键字声明的变量不会被束缚在代码块中
1 if (true) {
2
3 var foo, bar = 'abc';
4 }
5
6 console.log(foo, bar)
1.1 使用let关键字定义变量
- 可以一次定义多个变量
- 定义时可以只声明不赋值
- 定义之后可以随时修改变量的值
- 使用 let 关键字定义的变量,变量声明不会被提升,因此我们需要先定义,后使用
- 在同一作用域下,不能重复定义同名变量
- 全局定义的变量会被作为全局对象(global/window)的属性
- 在代码块中使用 let 关键字声明的变量会被束缚在代码块中
1 // 0. 可以一次定义多个变量 2 // let foo = 123, bar = 'abc'; 3
4 // 1. 定义时可以只声明不赋值 5 // let foo, bar; 6
7
8 // 2. 定义之后可以随时修改变量的值 9 // let foo = 123, bar = 'abc'; 10 // foo = 'abc'; 11 // bar = 123; 12 // console.log(foo, bar); // 'abc' 123 13 14 // 3. 使用 let 关键字定义的变量,变量声明不会被提升,因此我们需要先定义,后使用 15 // console.log(foo); // foo is not undefined 16 // let foo = 123; 17 18 19 // 4. 在同一作用域下,不能重复定义同名变量 20 // let foo = 123; 21 // let foo = 'abc'; // Identifier 'foo' has already been declared 22 // console.log(foo) 23 24 // 5. 全局定义的变量会被作为全局对象(global/window)的属性 25 26 // 6. 在代码块中使用 let 关键字声明的变量会被束缚在代码块中
1.2 使用const关键字定义常量
1. 在使用 const 关键字声明常量时,必须要进行赋值(初始化)。
2. 常量一旦初始化后,就不能被修改。
3. 在同一作用域下,不能重复定义同名的常量。
4. 常量的声明不会被提升
5. 所有常量只在当前代码块内有效,一旦执行流到了代码块外,这些常量就会被立即销毁。6.不在Windows下
1 <script>
2
3 // 1. 在使用 const 关键字声明常量时,必须要进行赋值(初始化)。
4 // const foo = 123, bar = 'abc';
5 // const bar; // 抛出错误:Missing initializer in const declaration
6
7 // 2. 常量一旦初始化后,就不能被修改。
8 // const foo = 123;
9 // foo = 'abc'; // 抛出错误:Assignment to constant variable.
10
11 // 3. 在同一作用域下,不能重复定义同名的常量
12 // const foo = 123;
13 // const foo = 'abc'; // 抛出错误:Identifier 'foo' has already been declared
14
15 // 4. 常量的声明不会被提升
16 // console.log(foo) // 抛出错误:foo is not defined
17 // const foo = 123;
18
19 // 5. 所有常量只在当前代码块内有效,一旦执行流到了代码块外,这些常量就会被立即销毁。
20 // if (true) {
21 // const foo = 123;
22 // console.log(foo)
23 // }
24 // console.log(foo) // 抛出错误:foo is not defined
25
26 </script>
1.3 模块字面量
插入数据
1
1.4 展开操作符 (...)
给变量前面加三个点(...),就不再打印出数组格式了,(如控制台打印的一二行,第一行是使用展开操作符,第二行是没有使用展开操作符的)
使用场景:a.合并数组 b.复制数组
1
1.5 剩余操作符(又名不定参数)
不定参数:顾名思义,不知道还有多少个参数,剩余的统统接收,用来代替arguments;
函数中剩余操作符只能是函数中最后的一个形参,格式:...bar;
第一个实参会被赋值给第一个形参,剩余所有的实参都会被交给形参 bar,bar会自动变成一个数组;
1
二、解构
2.1解构的实用性
在 ECMAScript 5 或更早的版本中,从对象或数组中获取特定的数据并赋值给本地变量需要书写很多并且相似的代码。例如:
1 let options = {
2 repeat: true,
3 save: false
4 };
5
6 // 从对象中提取数据
7
8 let repeat = options.repeat,
9 save = options.save;
这段代码反复地提取在 options 上存储地属性值并将它们传递给同名的本地变量。虽然这些看起来不是那么复杂,不过想象一下如果你的一大批变量有着相同的需求,你就只能一个一个地赋值。而且,如果你需要从对象内部嵌套的结构来查找想要的数据,你极有可能为了一小块数据而访问了整个数据结构。
这也是 ECMAScript 6 给对象和数组添加解构的原因。当你想要把数据结构分解为更小的部分时,从这些部分中提取数据会更容易些。很多语言都能使用精简的语法来实现解构操作
2.2 对象解构 -解构赋值 ( { } 花括号)
声明的变量是同名变量,写在{}里即可,变量之间用“,”隔开就行;
声明的变量是非同名变量,在{}里写上" “:”+ “变量名” " ,变量之间用“,”隔开 例如:
let { firstName: first_name, lastName: last_name } = obj;
为变量指定默认值,当对象中没有某个属性时,直接写上变量的名字加“=”即可, 例如:
let { firstName, lastName, myAge = 20 } = obj; let { firstName: first_name, lastName: last_name, myAge: my_age = 20} = obj;
为已有变量重新赋值,({}=obj),因为JS解析引擎不允许操作赋值符(=)左边出现花括号所以使用 "()"将整个赋值语句包起来,使他成为一个表达式
嵌套对象解构,(俄罗斯套娃,哈哈~~)例如:obj里面的phone里面的number赋值给同名变量number
let { phone: { number, brand } } = obj;
1
2.3 数组解构([ ]中括号)
数组解构初始化变量:数组中的元素会按照顺序赋值给变量
使用数组解构为变量重新赋值,[ ]=colors,
[ firstColor, secondColor ] = colors;我们
为变量指定默认值,当数组中没有第四个元素。而我们又需要第四个元素时,我们可以给变量赋值,但是当元素中有第四个元素,我们再给第四个元素赋值是无效的;
跳过数组中的指定元素,当我们不需要第二,三个元素,又需要第四个元素,中间可以用“ ,”隔开,
嵌套解构前面不需要的数据用 “ ,”隔开,嵌套的用“ [ ]”l来表示
1
2.4 混合解构
1 <script>
2
3 let node = {
4 type: 'Identifier',
5 name: 'foo',
6 loc: {
7 start: {
8 line: 1,
9 column: 1
10 },
11 end: {
12 line: 4,
13 column: 4
14 }
15 },
16 range: [0, 3]
17 };
18
19 // 把 node 对象中 loc 对象的 start 属性的值赋值给变量 start,把 range 数组中第 2 个元素赋值给变量 endIndex
20 let { loc: { start }, range: [, endIndex] } = node;
21
22 console.log(start, endIndex)
23
24 </script>
2.5 参数解构
1 <script>
2 // 1. 以普通方式传参时,相当于把 obj 对象赋值给形参 student
3 // student = obj
4
5 // function getFullName (student) {
6 // return student.firstName + ' · ' + student.lastName;
7 // }
8
9 // let obj = {
10 // firstName: '刘旭凯',
11 // lastName: '约翰尼'
12 // };
13
14 // console.log(getFullName(obj))
15
16
17 // 2. 以对象解构的方式传参
18 // 把 obj 对象中的 firstName 属性的值传递给形参 firstName,把 lastName 属性的值传递给形参 lastName
19 // { firstName, lastName } = obj;
20
21 // function getFullName ({ firstName, lastName }) {
22 // return firstName + ' · ' + lastName;
23 // }
24
25 // let obj = {
26 // firstName: '刘旭凯',
27 // lastName: '约翰尼'
28 // };
29
30 // console.log(getFullName(obj))
31
32 // 3. 为形参指定默认值
33 // 当 obj 对象没有 lastName 属性时,形参 lastName 就会使用默认值 '尼古拉斯'
34 function getFullName ({ firstName, lastName = '尼古拉斯' }) {
35 return firstName + ' · ' + lastName;
36 }
37
38 let obj = {
39 firstName: '刘旭凯',
40 lastName: '克里斯蒂安'
41 };
42
43 console.log(getFullName(obj))
44
45
46 </script>
三、函数
3.1 默认参数
ES5 语法中,为函数形参指定的默认值写法
1 <script>
2 // 1. 在 ES5 语法中,为函数形参指定默认值的写法:
3 // 写法一:
4 function foo (bar) {
5 bar = bar || 'abc';
6 console.log(bar)
7 }
8 foo('xyz')
9
10 // 写法二:
11 function foo (bar) {
12 if (typeof bar === 'undefined') {
13 bar = 'abc';
14 }
15 console.log(bar)
16 }
17
18 foo('xyz');
19
20
21
22 </script>
ES6中为函数形参指定默认值
a.除了为形参直接指定默认值以外,形参的默认值还可以是表达式,例如,timeout = 5 * 1000
b.在预编译阶段,形参表达式不会执行,只有在调函函数,并且没有为形参传递实参的情况下才执行
1 <script>
2 // 2. 使用 ES6 的语法为函数形参指定默认值
3 function post (url, data = {}, timeout = foo * 1000) {
4 console.log(arguments)
5 console.log(url, data, timeout)
6 }
7
8 // post('xyz', {uname: 'zhangsan', upass: '123'}, 3000);
9 post('xyz', null, 3000);
10
11 // 注意事项:
12 // 1> 除了为形参直接指定默认值以外,形参的默认值还可以是表达式,例如,timeout = 5 * 1000
13 // 2> 在预编译阶段,形参表达式不会执行,只有在调函函数,并且没有为形参传递实参的情况下才执行。
14
15 </script>
3.2 不定参数
1 <script>
2
3 // 不定参数,使用剩余操作符接收剩余的实参,这些实参会被保存到一个不定参数(args)中
4 function foo (...args) {
5 return args.reduce(function (previousValue, currentValue) {
6 console.log(previousValue, currentValue)
7 return previousValue += currentValue;
8 })
9 }
10
11 // 将上面的函数改成箭头函数的形式
12 var foo = (...args) => args.reduce((a, b) =>a += b)
13
14
15 console.log(foo(1, 32, 34, 5, 6))
16
17 </script>
previousValue:上一个值
currentValue:当前的值
reduce()方法:
1.接收一个函数作为累加器,将数组元素计算为一个值(从左到右),
2.需要接收四个参数( 必需:1.acc 累加器 2.cur 当前值 ;可选:1.idx 当前索引 2 .src 源数组)
3.3 箭头函数
凡是以后遇到匿名函数都可以使用箭头函数
省略等号和function
如果形参数量为 0,则必须加上小括号。箭头后面的表达式的结果会被作为函数的返回值。
如果形参的数量为 1,则可以省略小括号。
如果形参数量大于 1,则不能省略小括号。
如果函数的执行体比较简单(直接量或表达式),可以省略大括号,箭头后面的直接量或表达式会被自动作为返回值
如果函数的执行体比较复杂,则不能省略大括号。
1
3.4 箭头函数没有this绑定
普通函数作用域中的 this 已经被绑定成 window 对象,因此当我们放问 this 时,直接在当前作用域下就能访问的到。
箭头函数的作用域中没有绑定 this,因此,当我们访问 this 时,会去上一层作用域中查找 this。
1
3.5 箭头函数没有arguments绑定
1 <script>
2
3 // 普通函数
4 var foo = function (greeting) {
5 console.log(arguments)
6 return greeting;
7 };
8
9 // 箭头函数中没有绑定 arguments 对象,因此下面的输入语句会报错:arguments is not defined
10 var bar = (greeting) => {
11 console.log(arguments)
12 return greeting;0
13 };
14
15 console.log(foo('Hello World!'))
16 console.log(bar('你好世界!'))
17
18 </script>
3.6 箭头函数中不能手动绑定this
1 <script>
2
3 // this 指向的对象
4 var obj = {
5 fullName: '谭文华'
6 };
7
8 // 1. 普通函数,可以使用 call() 方法改变函数中 this 的绑定
9 // var foo = function (greeting) {
10 // return this.fullName + '说:' + greeting;
11 // };
12 // console.log(foo.call(obj, 'Hello World!'))
13
14
15 // 2. 箭头函数,不能使用 call() 方法改变函数中 this 的绑定,箭头函数中不能绑定 this。
16 var bar = (greeting) => {
17 return this.fullName + '说:' + greeting;
18 };
19
20 // 下面的代码不会报错,但是也不起作用
21 console.log(bar.call(obj, '你好世界!'))
22
23 </script>
3.7 new.target
1 <script>
2 // 1. ECMAScript 5 中判断构造函数是否通过 new 关键字调用的写法
3 // function Person (fullName) {
4 // if (this instanceof Person) {
5 // this.fullName = fullName;
6 // } else {
7 // return new Person(fullName);
8 // }
9 // }
10
11 // let student = Person('孟天乐')
12
13 // 2. ECMASript 6 引入一个 new.target 属性,当我们使用 new 操作符调用构造函数时,new.target 属性的值为构造函数,否则为 undefined
14 // function Person (fullName) {
15 // if (typeof new.target !== 'undefined') {
16 // this.fullName = fullName;
17 // } else {
18 // throw new Error('必须通过 new 关键字来调用 Person。');
19 // }
20 // }
21 // let student = new Person('孟天乐');
22 // console.log(student)
23
24 // 3. 除此之外,还可以检查 new.target 是否被某个特定构造函数所有调用。
25 // 例如,Person 构造函数中的 new.target 属性的值被限定为 Person
26 // function Person (fullName, age) {
27 // if (typeof new.target === Person) {
28 // this.fullName = fullName;
29 // this.age = age;
30 // } else {
31 // throw new Error('必须通过 new 关键字来调用 Person。');
32 // }
33 // }
34
35 // function Dog (fullName, age) {
36 // Person.call(this, fullName, age)
37 // }
38
39 // let dog = new Dog('HeHe', 3)
40
41 // console.log(dog)
42
43 // 4. 不能在函数外部使用 new.target,否则会报错
44 function Person () {
45 console.log(new.target)
46 }
47
48 // 下面代码会抛出错误:new.target expression is not allowed here
49 // console.log(new.target)
50
51 let student = new Person('崔景龙')
52
53 </script>
四、对象
4.1 对象-属性初始值的简写
当一个对象的属性与本地变量同名时,不需要再写冒号和值,直接写属性名即可
1
4.2 对象方法的简写
1 <script>
2
3
4 // 在 ES 5 中,如果为对象添加方法,必须要通过指定名称并完整定义函数来实现。
5 let obj = {
6 fullName: '杨柯',
7 sayName: function () {
8 return this.fullName;
9 }
10 };
11
12 // 在 ES 6 中,语法更简洁,取消了冒号和 function 关键字。如下所示:
13 let obj = {
14 fullName: '杨柯',
15 sayName () {
16 return this.fullName;
17 }
18 };
19
20
21 </script>
4.3 可计算属性名
1 <script>
2
3 // 在对象字面量中使用方括号表示该属性名是可计算的,方括号中的内容会被计算求值,最终转化成一个字符串,该字符串就是最终的属性名。
4 let suffix = 'name';
5
6 let person = {
7 ['first' + suffix]: '杨帅',
8 ['last' + suffix]: '泰坦尼'
9 };
10
11 console.log(person)
12
13 </script>
4.4 对象 新增的两个方法
1 <script>
2
3
4 // 1. 在有些情况下,既是全等运算符比较出来的结果也是不正确的。例如,在下面两种情况下:
5
6 // +0 和 -0 在 JS 解析引擎中被表示为两个完全不同的实体,而如果使用全等运算符(===)对两者进行比较,得到的结果是两者相等。
7 console.log(+0 == -0); // true
8 console.log(+0 === -0); // true
9 console.log(Object.is(+0, -0)); // false
10
11 // NaN 和 NaN 在 JS 解析引擎中被表示为两个完全相同的实体,但是无论使用等于(==)还是全等(===),得到的结果都是 false。
12 console.log(NaN == NaN); // false
13 console.log(NaN === NaN); // false
14 console.log(Object.is(NaN, NaN)); // true
15
16 // 在大多数情况下,Object.is() 方法的比较结果与全等运算符完全相同,唯一的区别在于 +0 和 -0 会被识别为不相等,NaN 和 NaN 会被识别为相等。
17
18 // 2. Object.assign() 方法可以接收任意数量的源对象(obj2,obj3),并按照指定的顺序将属性复制到接收对象(obj1)。
19 // 如果多个源对象具有同名属性,则排位靠后的源对象会覆盖排外靠前的对象。
20
21 let obj1 = {
22 fullName: '陈世壮',
23 sayName () {
24 return this.fullName;
25 }
26 };
27
28 let obj2 = {
29 fullName: '任俊玖',
30 age: 20
31 };
32
33 let obj3 = {
34 fullName: '朱亚鹏',
35 gender: '男'
36 };
37
38 // 通过自定义方法实现了一个可以合并多个对象的方法,
39 // function mixin(receiver, ...suppliers) {
40 // suppliers.forEach(supplier => {
41 // Object.keys(supplier).forEach(key => {
42 // receiver[key] = supplier[key]
43 // })
44 // })
45 // return receiver;
46 // }
47 // console.log(mixin(obj1, obj2, obj3))
48
49 // 使用 ES6 新增 Object.assgin() 方法将多个对象的属性合并到第一个对象中。
50 // Object.assign(obj1, obj2, obj3);
51
52 // console.log(obj1)
53 // console.log(obj2)
54 // console.log(obj3)
55
56 浅拷贝:(拷贝的是地址)引用类型拷贝的都是地址
// 使用 ES6 新增 Object.assgin() 方法将多个对象的属性合并到第一个对象中。
let obj1={
hobby:['eat', 'sleep']
}
let obj2=Object.assign({},obj1);
obj2.hobby[0]='play'
console.log(obj2);
console.log(obj1);
57 58 </script>
4.5 重复的对象字面量属性
1 <script>
2
3 // 对于每一组重复属性,都会选取最后一个取值。
4 let obj = {
5 fullName: '陈世壮',
6 fullName: '李忠易',
7 age: 18,
8 age: 20
9 };
10
11 console.log(obj.fullName); // '李忠易'
12 console.log(obj.age); // 20
13
14 </script>
4.6 自有属性枚举顺序
1 <script>
2
3 // ES 5 中并没有定义对象属性的枚举顺序,有 JavaScript 引擎厂商自行决定。
4 // ES 6 中明确规定了对象的自有属性被枚举时的返回顺序。
5 // 自有属性枚举顺序的基本规则:
6 // 1. 所有数字按升序
7 // 2. 所有字符串按照它们被加入对象时的顺序排序
8
9 let obj = {
10 a: 1,
11 0: 2,
12 c: 3,
13 2: 4,
14 b: 5,
15 1: 6
16 };
17
18 console.log(Object.getOwnPropertyNames(obj)); // ["0", "1", "2", "a", "c", "b"]
19
20 </script>
4.7 改变对象的属性
1 <script>
2
3 let person = {
4 getGreeting () {
5 return 'Hello';
6 }
7 };
8
9 let dog = {
10 getGreeting () {
11 return 'woof';
12 }
13 };
14
15 // 使用 create() 方法将 person 对象作为原型对象
16 let friend = Object.create(person); // {}
17 console.log(friend.getGreeting()); // 'Hello'
18 console.log(Object.getPrototypeOf(friend) === person); // true
19
20 // 使用 setPrototypeOf() 方法将 friend 对象的原型对象修改成 dog
21 Object.setPrototypeOf(friend, dog);
22 console.log(friend.getGreeting()); // 'Hello'
23 console.log(Object.getPrototypeOf(friend) === dog); // true
24
25 </script>
4.8 使用super关键字访问原型对象(构造函数类)
1 <script>
2
3 let person = {
4 getGreeting () {
5 return 'Hello';
6 }
7 };
8
9 let dog = {
10 getGreeting () {
11 return 'woof';
12 }
13 };
14
15 // let friend = {
16 // getGreeting () {
17 // return Object.getPrototypeOf(this).getGreeting.call(this) + ', hi';
18 // }
19 // };
20
21 // ES 6 引入了 super 关键字,super 指向当前对象的原型对象,实际上也就是 Object.getPrototypeOf(this) 的值,于是,上面的代码可以简化成如下形式:
22 let friend = {
23 getGreeting () {
24 return super.getGreeting() + ', hi';
25 }
26 };
27
28 // 使用 setPrototypeOf() 方法将 friend 对象的原型对象修改成 person
29 Object.setPrototypeOf(friend, person);
30 console.log(friend.getGreeting()); // 'Hello'
31 console.log(Object.getPrototypeOf(friend) === person); // true
32
33 // 使用 setPrototypeOf() 方法将 friend 对象的原型对象修改成 dog
34 Object.setPrototypeOf(friend, dog);
35 console.log(friend.getGreeting()); // 'Hello'
36 console.log(Object.getPrototypeOf(friend) === dog); // true
37
38
39
40 </script>
五、类
5.1 ES5 中与类相似的结构
1 <script>
2
3 // 构造函数类似于面向对象编程语言中类(class)
4 function PersonType(fullName) {
5 this.fullName = fullName;
6 }
7
8 Object.defineProperty(PersonType.prototype, 'sayName', {
9 configurable: true,
10 enumerable: false,
11 writable: true,
12 value: function () {
13 return this.fullName;
14 }
15 })
16
17 let student1 = new PersonType('苑文浩');
18
19 console.log(student1)
20
21
22 </script>
5.2 ES6 中新增类的概念
1 <script>
2
3 // 使用 class 关键字定义 PersonClass 类
4 class PersonClass {
5 // PersonClass 类的构造函数,等价于构造函数 PersonType
6 // 当我们创建 PersonClass 的实例时,该方法会自动被调用。
7 constructor(fullName) {
8 this.fullName = fullName;
9 }
10
11 // 为原型对象添加的方法,等于下面的代码:
12 // Object.defineProperty(PersonType.prototype, 'sayName', {
13 // configurable: true,
14 // enumerable: false,
15 // writable: true,
16 // value: function () {
17 // return this.fullName;
18 // }
19 // })
20
21 sayName() {
22 return this.fullName;
23 }
24 }
25
26 let student1 = new PersonClass('刘旭凯');
27
28 console.log(student1)
29
30 // 通过与之前的构造函数对比,类和构造函数的作用是一样的。
31 // 其实类的语法只是一种语法糖,实质上等价于一个自定义类型的构造函数。
32
33 </script>
5.3 类和构造函数的区别
1. 类的声明不会被提升,而构造函数的声明会被提升。
2. 使用除 new 以外的方式调用类的构造函数(constructor)会导致程序抛出错误。
3. 在类中声明的方法都是不可枚举的。
5.4 类-构造函数的属性
1 <script>
2
3 function PersonType(fullName) {
4 this.fullName = fullName;
5 // 实例对象的方法
6 this.greeting = function () {
7 return 'Hello World!';
8 };
9 }
10
11 // 构造函数的方法
12 PersonType.create = function (fullName) {
13 return new PersonType(fullName)
14 };
15
16 // 原型对象的方法
17 Object.defineProperty(PersonType.prototype, 'sayName', {
18 configurable: true,
19 enumerable: false,
20 writable: true,
21 value: function () {
22 return this.fullName;
23 }
24 });
25
26 // 构造函数的方法,可以在不创建实例的情况下直接使用。
27 // PersonType.create('朱帅旗')
28
29 // 在使用实例对象的属性时,必须要先创建实例对象。
30 // let student1 = new PersonType('苑文浩');
31 // console.log(student1.fullName)
32 // console.log(student1.sayName())
33
34 </script>
5.5 类-类的静态方法
1 <script>
2
3 // 类的语法
4 class PersonClass {
5
6 constructor(fullName) {
7 this.fullName = fullName;
8 // 实例对象的方法
9 this.greeting = function () {
10 return '你好世界!';
11 };
12 }
13
14 // PersonClass 类的静态方法,等价于构造函数的方法
15 static create (fullName) {
16 return new PersonClass(fullName);
17 }
18
19 // 原型对象的方法
20 sayName () {
21 return this.fullName;
22 }
23
24 }
25
26 // 类的静态方法,不用创建实例即可调用
27 PersonClass.create('张尊娟')
28
29 // 在使用实例对象的属性时,必须要先创建实例对象。
30 let student1 = new PersonClass('张康');
31 console.log(student1.fullName)
32 console.log(student1.sayName())
33
34 </script>
5.6 类-构造函数的继承
1 <script>
2
3 // 声明 Rectangle 构造函数
4 function Rectangle (width, height) {
5 this.width = width;
6 this.height = height;
7 }
8
9 Object.defineProperty(Rectangle.prototype, 'getArea', {
10 enumerable: false,
11 configurable: true,
12 writable: true,
13 value: function () {
14 return '矩形面积为:' + this.width * this.height;
15 }
16 });
17
18 // 声明 Square 构造函数
19 function Square (size) {
20 Rectangle.call(this, size, size);
21 }
22
23 // 让 Square 构造函数继承 Rectangle 构造函数
24 Square.prototype = Object.create(Rectangle.prototype, {
25 constructor: {
26 configurable: true,
27 enumerable: false,
28 writable: true,
29 value: Square
30 }
31 });
32
33 let square = new Square(20);
34
35 console.log(square)
36 </script>
5.7 类-类的继承
1 <script>
2
3 class Rectangle {
4
5 constructor(width, height) {
6 this.width = width;
7 this.height = height;
8 }
9
10 getArea () {
11 return '矩形面积为:' + this.width * this.height;
12 }
13 }
14
15 // 让 Square 类继承 Rectangle 构类
16 class Square extends Rectangle {
17
18 constructor (size) {
19 // 等价于 Rectangle.call(this, size, size)
20 super(size, size)
21 }
22 }
23
24 let square1 = new Square(20)
25
26 // square1.__proto__ --> obj.__proto__ --> Rectange.prototype.__proto__ --> Object.prototype
27
28 console.log(square1)
29
30 </script>
5.8 类-类的构造函数
1 <script>
2
3 // 1. 当我们创建某个类的实例时,类的构造函数(constructor)会自动被调用。
4 class Rectangle {
5
6 constructor(width, height) {
7 this.width = width;
8 this.height = height;
9 }
10
11 getArea() {
12 return '矩形面积为:' + this.width * this.height;
13 }
14 }
15
16 // 2. 如果子类中具有构造函数,则必须在构造函数中使用 super 方法调用父类的构造函数,并传入所需的参数。
17 class Square extends Rectangle {
18 constructor (size) {
19 super(size, size)
20 }
21 }
22
23
24 // 3. 构造函数不是必需的,当我们不需要为实例对象初始化属性时,可以省略构造函数。
25 // 当创建类的实例时,会自动地为类添加一个默认的 constructor 构造函数,并在构造函数中使用 super() 方法调用父类的构造函数,并传入所有参数。
26 // class Square extends Rectangle {
27
28 // }
29
30 // JS 引擎在后台为我们做了哪些事情呢?下面的代码等价于上面的代码:
31 // class Square extends Rectangle {
32 // constructor (...args) {
33 // super(...args)
34 // }
35 // }
36
37 let square1 = new Square(20)
38
39 console.dir(Square)
40 console.log(square1)
41
42 </script>
5.9 覆盖父类的方法
1 <script>
2
3 //
4 class Rectangle {
5
6 constructor(width, height) {
7 this.width = width;
8 this.height = height;
9 }
10
11 getArea () {
12 return '矩形面积为:' + this.width * this.height;
13 }
14 }
15
16 //
17 class Square extends Rectangle {
18 constructor (size) {
19 super(size, size)
20 }
21
22 // 创建一个与父类同名的方法就可以覆盖父类的方法。
23 // 在顺着原型对象链查找方法时,该方法会被优先找到。
24 getArea () {
25 return '正方形的面积为:' + this.width * this.height;
26 }
27 }
28
29 let square1 = new Square(20)
30
31 console.log(square1)
32 console.log(square1.getArea())
33
34 </script>
5.10 调用父类的方法
1 <script>
2
3 //
4 class Rectangle {
5
6 constructor(width, height) {
7 this.width = width;
8 this.height = height;
9 }
10
11 getArea () {
12 return '矩形面积为:' + this.width * this.height;
13 }
14 }
15
16 //
17 class Square extends Rectangle {
18 constructor (size) {
19 super(size, size)
20 }
21
22 // 在子类中使用 super 关键字就可以调用父类的方法
23 // Rectangle.prototype.getArea.call(this).replace('矩形', '正方形');
24 getArea () {
25 return super.getArea().replace('矩形', '正方形');
26 }
27
28 }
29
30 let square1 = new Square(20)
31
32 console.log(square1)
33 console.log(square1.getArea())
34
35 </script>
5.11 继承类的静态方法
1 <script>
2
3 class Animal {
4 constructor(name) {
5 this.name = name;
6 }
7
8 static greeting () {
9 return 'Hello';
10 };
11 }
12
13 class Dog extends Animal {
14 constructor (name) {
15 super(name)
16 }
17 }
18
19 console.log(Animal.greeting());
20
21 // 父类的静态方法也会被子类继承,因此我们可以通过 Dog 调用父类的 greeting() 方法。
22 console.log(Dog.greeting());
23
24 </script>
5.12 ES5中自己实现的模块
1 <script>
2
3 // 1、张三和李四在项目开发中遇到的问题如下:
4
5 // 张三创建了一个 index.js 文件,内容如下:
6 let foo = 123;
7
8 // 李四创建了一个 home.js 文件,内容如下:
9 let foo = 'abc';
10
11 // 当 index.js 和 home.js 文件同时引入到一个 index.html 文件中时,就会抛出错误。
12
13 // 2、ES 5 没有模块的概念,但是我们又需要类似于模块的功能,于是我们想了一系列的方式去自己实现模块的功能。
14 // 最常用的一种方式就是利用立即执行的函数表达式及其作用域实现模块的功能。例如:
15
16 // 在 index.js 中创建模块 module1
17 let module1 = (function() {
18
19 let foo = 123;
20
21 function greeting () {
22 return 'Hello';
23 }
24
25 return {
26 foo,
27 greeting
28 };
29
30 }())
31
32 // 在 home.js 中创建模块 module2
33 let module2 = (function() {
34
35 let foo = 'abc';
36
37 function greeting () {
38 return 'Hello';
39 }
40
41 return {
42 foo,
43 greeting
44 };
45
46 }())
47
48 // 我们只需要保证两个模块的名称不同即可,在使用模块中的函数或变量时,只需要在前面加上模块名即可,如下所示:
49 console.log(module1.foo)
50 console.log(module2.foo)
51
52
53
54 </script>
5-13 ES6模块化规范
1 <body>
2
3 <!-- 要想让浏览器把 module1.js 和 module2.js 当成两个模块去解析,你要为 script 标签添加 type="module" 属性 -->
4 <!-- 要想然下面的代码正确执行,不能使用文件协议(file://),也就是说不能直接使用浏览器打开该页面,需要将其放入服务器中,然后再打开。 -->
5 <script src="./13-module1.js" type="module"></script>
6 <script src="./14-module2.js" type="module"></script>
7 <script src="./15-module3.js" type="module"></script>
8 </body>
module1.js
1 // 崔景龙 2 3 // 1. 模块私有的内容 4 // function multiply (x, y) { 5 // return x * y; 6 // } 7 8 9 // 2. 模块导出的内容 10 // export function sum (x, y) { 11 // return x + y; 12 // } 13 14 // export function substract (x, y) { 15 // return x - y; 16 // } 17 18 // 3. 一次导出多个模块成员 19 function divide (x, y) { 20 return x / y; 21 } 22 23 function remainder (x, y) { 24 return x % y; 25 } 26 27 export { divide, remainder }; 28 29 // 4. 为导出成员重命名 30 // export { divide as div, remainder as rem }; 31 32 33 // 5. 默认导出,模块默认导出成员,其他模块导入该成员时,名称不能写到大括号中,并且无需与导出时的名称一致。 34 // export default function max (x, y) { 35 // return x > y ? x : y; 36 // } 37 38 39 // 6. 每个模块只能有一个默认导出,因此下面的代码会抛出错误。 40 // export default function min (x, y) { 41 // return x < y ? x : y; 42 // }
module2.js
1 // 刘旭凯 2 3 // 1. 从 module1.js 中导入 sum() 和 substract() 方法 4 // import { sum, substract } from './13-module1.js'; 5 6 // 使用从 module1.js 中导入的方法 7 // console.log(sum(12, 48)) 8 // console.log(substract(12, 48)) 9 10 // 2. 导入重命名之后的模块成员 11 // 注意,必须要写成重命名之后的名称(div,rem),不能写成重命名之前的名称(divide,remainder) 12 // import { div, rem } from './13-module1.js'; 13 // console.log(div(48, 12)) 14 // console.log(rem(48, 12)) 15 16 // 3. 导入其他模块默认导出成员 17 // 注意,名称不能写到大括号中,并且无需与导出时的名称一致。 18 // import abc from './13-module1.js'; 19 // console.log(abc(12, 48)) 20 21 // 4. 同时导入其他模块的默认导出的成员和普通导出的成员 22 // import abc, {sum, div} from './13-module1.js' 23 // console.log(abc(12, 48)) 24 // console.log(sum(12, 48)) 25 // console.log(div(12, 48)) 26 27 // 5. 为导入的成员重命名 28 // 注意,重命名之后不能再使用之前的名称了,否则会抛出错误。 29 // import { substract as sub} from './13-module1.js'; 30 // console.log(sub(12, 48)) 31 // console.log(substract(12, 48)); // 抛出错误 32 33 // 6. 导入其他模块导出的所有成员 34 // import * as module1 from './13-module1.js' 35 // console.log(module1.sum(12, 34)) 36 // console.log(module1.default(12, 34)) 37 38 // 7. 导入没有导出任何成员的模块,目的是想让模块中的代码执行 39 // 注意,模块中的代码只在第一次被导入时执行。 40 // import './13-module1.js'; 41 42 // 下面是一个简单的使用场景: 43 44 // module1.js 文件中有如下代码: 45 // let age = 20; 46 47 // export function getAge () { 48 // return age; 49 // } 50 51 // export function setAge () { 52 // age++; 53 // } 54 55 // module2.js 文件中的代码如下: 56 // import { setAge } from './13-module1.js'; 57 // setAge(); 58 59 // module3.js 文件中的代码如下: 60 // import './module2.js'; 61 // import { getAge } from './module1.js'; 62 // console.log(getAge()); // 21 63 64 // 8. 将导入的成员重命名之后再次导出 65 export { divide as div, remainder as rem } from './13-module1.js';
module3.js
1 // 导入 module2.js 模块中导出的成员(对应 module2.js 文件中第8个示例) 2 import { div, rem } from './14-module2.js'; 3 console.log(div(48, 12)) 4 console.log(rem(48, 12))