# 1.作用域
1.全局作用域
<script>标签和.js文件的最外层就是全局作用域,在全局作用域中声明的变量,任何其他作用域都可以被访问。
注意:window对象的属性也为全局变量 函数中未使用任何关键字声明的变量也为全局变量
2.局部作用域
局部作用域分为函数作用域和块作用域
(1)函数作用域:
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
//函数的参数也是函数内部的局部变量,不同函数内部声明的变量无法互相访问,函数执行完毕后,函数内部的变量实际被清空。
(2)块作用域
在javascript中使用"{}"包裹的代码被称为代码块,使用let/const声明的变量,在{}中会产生块作用域 对象不形成作用域?
块级作用域举例:
// if 和 for 是语句,var不会产生作用域,但是内部使用let声明变量,会产生块级作用域
1,for (let i = 0; i <= 3; i++) {
// 块级作用域
console.log(i); // 0 1 2 3
}
2. if (true){
let i = 10
}
console.log(i) //undefined
// Scope 作用域
// Global 全局
// Local 局部作用域
// Block 块级作用域
// Closure 闭包
1
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
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
# 2.let const var 的区别 (易考面试题) ⭐⭐⭐⭐⭐
1.let / const 能形成块级作用域, var不可以
2.let / const 不能变量提升,var可以
3.let / const 不能声明前使用,var可以
let / const 存在暂时性死区(temporal dead zone)
4.let / const 不能重复声明, var可以
5.在浏览器中, vars声明的全局变量会挂载在window对象上, let / const不会
6.const声明后必需马上赋值
7.const声明的变量不能改变值(简单数据类型不能改变值,引用数据类型不能改变地址)
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 3.作用域链
作用域链本质上是底层的变量查找机制
即函数在执行的时候,优先在当前函数作用域查找变量,如果当前函数作用域找不到,则会依次逐级到父级作用域乃至全局作用域中去查找
1
2
3
2
3
# 4.垃圾回收机制和内存泄漏
垃圾回收机制:Garbage Collection (简称GC)
JS的内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
全局变量一般不会回收(关闭页面回收),一般情况下,局部变量的值不用了,就会被自动回收
内存泄漏:不再用到的内存,没有及时释放。
1
2
3
4
5
2
3
4
5
# 5.浏览器垃圾回收算法:引用计数法和标记清除法
(1)引用计数法
算法:
// 1. 跟踪记录每个值被引用的次数。
// 2. 如果这个值的被引用了一次,那么就记录次数1
// 3. 多次引用会累加。
// 4. 如果减少一个引用就减1。
// 5. 如果引用次数是0 ,则释放内存。
缺陷: 循环引用
即堆内存空间的对象互相引用,计数永远也不能为0 无法被回收,内存泄漏。
(2)标记清除法
// 标记清除算法
// 主要将GC的过程分为两个阶段
// 1. 标记阶段:标记空间中的活动对象和非活动对象
// 2. 清除阶段:回收非活动对象,也就是销毁非活动对象
// 标记阶段从一组根元素开始,
// 递归遍历(一层一层的)这组根元素,
// 在这个遍历过程中,能访问到的元素称为活动对象,不能访问到的可以判断为垃圾数据。
缺陷: 内存碎片化 解决方式:标记整理法 Mark-compact
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 6.闭包 (易考面试题)⭐⭐⭐⭐⭐
闭包:内层函数引用外层函数的变量的集合。
闭包 = 内层函数 + 外层函数的变量
闭包的基本格式:
function out() {
let a = 10
function innner() {
console.log(a)
}
return innner
}
out()
闭包的作用:
1.可以让外部作用域访问到函数内部的变量
2.实现了数据的私有化
闭包的缺陷:会造内存泄漏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 7.arguments动态参数
// arguments他是函数独有的,内置的(默认就有的)一个对象
// 伪数组:属性名为索引,有length,没有pop push等数组的方法
// 它接收了函数调用的时候 传过来的所有实参
function sum(a, b, c, d, e) {
console.log(arguments) //伪数组:[]
console.log(arguments.length)
let count = 0
for (let i = 0; i < arguments.length; i++) {
count += arguments[i]
}
console.log(count)
// 错误演示:
// arguments.push(999)
}
sum(1, 2, 3, 4, 5)
sum(1, 2)
sum(1, 2, 3, 4, 5, 6, 7, 8, 9)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 8.rest剩余参数
// 剩余参数
// ...变量名
// arr是一个真数组,存的是剩下的实参
// ...arr只能放在形参位置的最后面
function getSum(...arr) {
console.log(arr)
let c = 0
arr.forEach((item) => (c += item))
}
// getSum(3, 4, 5, 6) // [3,4,5,6]
// getSum(6, 8, 5, 6, 4, 43, 2) // [6, 8, 5, 6, 4, 43, 2]
function getSum1(a, b, ...arr1) {
console.log(arr1)
}
getSum1(3, 4, 5, 6) // [5,6]
getSum1(3, 4, 5, 6, 6, 7, 3) //[5, 6, 6, 7, 3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 9.扩展运算符 : ...
用途1:求最值
let arr1 = [3,4,5,6,7,8,9]
console.log(Math.max(...arr1))
用途2:赋值数组 合并数组
(1)赋值数组
// const a1 = [2,3]
// const a2 = a1
// a2[0] = 999
// console.log(a1); [999,3] //a2改变 a1也会改变
// const a3 =[5,6,7]
// const a4 = [...a3]
// console.log(a4);
// a4[0]= 888
// console.log(a3); //[5,6,7]
// console.log(a4); //[888,6,7] //a4改变 a3不会改变
(2)合并数组
// const a5 =[5,6,7]
// console.log([...a5]); // [5,6,7]
// const a6 =[8,9,10]
// const newArr = a5.concat(a6) //concat 合并数组
// // console.log(newArr); // [5, 6, 7, 8, 9, 10]
// const newArr2 =[...a5,...a6]
// console.log(newArr2);
用途3:字符串展开与反转
const str = "hello world";
// console.log(str); //hello world
// console.log(...str); //h e l l o w o r l d
// console.log([...str]);
// console.log(str.split('')); //将 hello world 用空格隔开 并转为一个数组
// console.log(str.split('').reverse()); //数组顺序翻转
// console.log(str.split('').reverse().join('')); //将字符连接起来
// console.log([...str].reverse().join(''));
// const str1 = "h?e?l?l?o89dw?d"
// console.log(str1.split('?')); //表示str1字符串用?隔开
用途4:伪数组转换为真数组
// const divs = document.querySelectorAll("div"); //伪数组
// console.log(divs);
// divs.push(666) //报错 divs为伪数组
// const divs1 = [...divs]; //将divs转变为真数组
// console.log(divs1);
// divs1.push(666);
// console.log(divs1); //有效
// const divs2 = Array.from(divs) //Array.form(伪数组) :将伪数组转换为真数组
// console.log(divs2);
1
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 10. 剩余参数 与 扩展运算符的区别

# 11.数组解构
含义: 数组解构就是将数组的单元值赋值给一系列变量的简洁语法
基础语法:
const [a.b,c] = [1,2,3]
console.log(a) // 1
console.log(b) // 2
console.log(c) // 3
经典用法:交互2个变量
let a = 1
let b = 3; // 此处必有分号
[b,a] = [a,b]
console.log(a) // 3
console.log(b) // 1
不同情况下的数组结构用法
(1) 变量多 单元值少的情况
const [a,b,c,d] =['小米','苹果','华为']
console.log(a) // 小米
console.log(b) // 苹果
console.log(c) // 华为
console.log(d) // undefined
(2)变量多 单元值少的情况
const [a,b,c] = ['小米','苹果','华为','格力']
console.log(a) // 小米
console.log(b) // 苹果
console.log(c) // 华为
(3)利用剩余参数解决变量少 单元值多的情况:
const [a,b,...tel] = ['小米','苹果','华为','格力','vivo']
console.log(a) // 小米
console.log(b) // 苹果
console.log(tel) // ['华为','格力','vivo']
(4) 按需导入,忽略某些返回值:
const [a,,c,d] = ['小米','苹果','华为','格力']
console.log(a) // 小米
console.log(c) // 华为
console.log(d) // 格力
(5)多维数组解构
const [a,[b,c]] = ['苹果'['小米','华为']]
console.log(a) // 苹果
console.log(b) // 小米
console.log(c) // 华为
1
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
35
36
37
38
39
40
41
42
43
44
45
46
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
35
36
37
38
39
40
41
42
43
44
45
46
# 12.对象解构
对象解构:将对象的属性和方法快速批量赋值给一系列变量的简洁语法。
const obj = {
name: '仙女',
age: 18,
}
// let age = 19
// obj.name
// 对象的解构赋值,是按照属性名来解构的,可以交换位置
// 解构属性的时候重命名,语法=>旧属性名:新属性名
const { age: newAge, name } = obj
console.log(name, newAge) // 仙女 18
// console.log(name,age); //age is not defined
多级对象解构:
const pig = {
name: 'xxx',
sss: {
xx: 23,
uu: '灌灌灌灌',
demo: {
test: 345,
},
},
age: 90,
}
// 分步解构
// const { name, age, sss } = pig
// const { xx, uu, demo } = sss
// const { test } = demo
// 分析步骤
// const { name, age, sss:newSSS } = pig
// const { xx, uu, demo:demo1 } = newSSS
// const { test } = demo1
// 合并:
const {
name,
age,
sss: {
xx,
uu,
demo: { test },
},
} = pig
console.log(name,age,xx);
1
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 13.箭头函数的this ⭐⭐⭐⭐⭐
箭头函数没有arguments对象
箭头函数不会创建自己的this,它只会从自己作用域的上一层沿用this
(1)
const sayHi = ()=>{
console.log(this) // window
}
(2)
btn.addEventListener('click',()=>{
console.log(this) // window
})
//此处回调函数若是普通函数 this指向的则是btn
(3)
const user ={
name:"小明",
walk:()=>{
console.log(this) // window
}
}
user.walk()
//此处若对象的方法使用的是普通函数 ,则this指向user
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 14.创建对象的三种方式
1.按照字面量的方式
const obj ={
name:'abc'
}
2.new Object()
const obj1 = new Object() // 创建了一个空对象
obj1.name = '444'
const obj2 = new Object({ name: '0000', age: 20 })
3.通过构造函数创建对象
function Person(age) {
this.name = 'ddd'
this.age = age
this.sayHi = function () {
console.log('哈哈哈')
}
}
const p = new Person(18)
const p1 = new Person(17)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 15.构造函数执行new的过程 (易考面试题) ⭐⭐⭐⭐⭐
1.创建一个空对象
2.让this指向这个空对象 const this ={}
3.给这个空对象添加方法和属性
4.return this 返回这个对象(this)
1
2
3
4
2
3
4
# 16.构造函数原理
构造函数:即把多个类型对象的公共属性、公共方法抽取放到一个函数里,这个函数即为构造函数,它用来初始化对象,可以快捷创建多个类似的对象。
构造函数约定:
①构造函数的命名必须以大写字母开头
②构造函数只能由"new"操作符来执行
实例化:使用new关键字调用函数的行为
1
2
3
4
5
6
2
3
4
5
6
# 17.实例成员 -- 静态成员
实例成员:实例对象的属性和方法
静态成员:构造函数的属性和方法 console.dir()才可看到
function Person(){
//实例对象动态添加属性
this.name ='小明'
this.age = 18
//实例对象动态添加方法
this.sayHi = function(){
console.log('大家好')
}
}
//静态属性
Person.eyes = 2
//静态方法
Person.walk = function(){
console.log('it's the beginning)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 18.Object的三个常用静态方法
1. Object.keys(数组名) //获得对象的所有键
2. Object.values(数组名) //获得对象的所有值
3. Object.assign(拷贝数组名,被拷贝数组名) //对象拷贝
1
2
3
2
3
# 19.Array的常用实例方法

arr.reduce(function(累计值,当前元素[,索引号][,源数组]){},起始值)
arr.every(cbfn) :如果所有元素都满足条件,返回true
arr.some(cbfn):只要有一个元素满足条件,返回true
arr.find(cbfn)
arr.findIndex(cbfn)
1
2
3
4
5
2
3
4
5
# 20.Array的常见静态方法
Array.from() :将伪数组转换为真数组
Array.isArray() :判断是否为数组 返回true 或 false
1
2
2
# 21.String的常见实例方法
str.split('分隔符') // 将字符串拆分为数组 与 arr.join('连接符') 相反
str.substring(需要截取的第一个字符的索引号[,结束的索引号]) //结束的索引号不包括被截取的字符 ,如果没有结束的索引号,则默认截取到最后一个字符
str.startsWith(检测字符串[,检测位置索引号]) // 检测是否以某字符开头
str.includes()//判断字符串中是否包含某个子字串
1
2
3
4
2
3
4
# 22.Number的常见实例方法
num.toFixed(保留的小数位的长度,四舍五入)
1
# 23.JS原型五条规则(必背)⭐⭐⭐⭐⭐
1.所有的引用类型(数组,函数,对象),都具有对象的特性,可以自由扩展属性。
const obj = {} // obj.a =100
const arr = [] // arr.a =100
function fn(){} // fn.a = 100
2.所有的对象,都有一个__proto__属性,属性值是一个普通对象。(__proto__ ==>[[prototype]] 隐式原型)
obj.__proto__
arr.__proto__
fn.__proto__
3.所有的函数,都有一个prototype属性,属性值是一个普通对象 (prototype属性 ==>显示原型)
fn.prototype // 箭头函数没有prototype属性
4.所有对象的隐式原型(__proto__)指向它的构造函数的显式原型(prototype)
obj.__proto__ === Object.prototype
arr.__proto__ === Array.prototype
fn.__proto__ === Function.prototype
5.当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么去它的__proto__中寻找。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 24.构造函数存在的问题 以及引入原型解决
// 构造函数存在浪费内存的问题(方法)
function Star(name, age){
this.name = name
this.age = age
this.sing = function(){
console.log('唱歌')
}
}
const kk = new Star('坤坤', 18)
const jl = new Star('杰伦', 20)
console.log(kk)
kk.sing()
jl.sing()
console.log(kk.sing === jl.sing) // false
// 每创建一个对象,都会在堆内存中新开辟一个空间,存储方法
// 这个时候,就存在浪费空间的问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

function Star(name, age){
this.name = name
this.age = age
// this.sing = function(){
// console.log('唱歌')
// }
}
// 公共的方法写到原型上
Star.prototype.sing = function(){
console.log('唱歌')
}
const ldh = new Star('刘德华', 20)
const zxy = new Star('张学友', 18)
ldh.sing()
zxy.sing()
// sing是一个方法,引用类型,比较的是地址是否相等,是否是同一个对象
console.log(ldh.sing === zxy.sing) // true 表示内存上是同一个方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 25. 原型prototype
原型:原始的模型
在JS里 原型就是一个对象,叫做原型对象 : 构造函数。prototype
1.所有函数都有一个prototype属性(显式原型),这个属性是一个指针,指向原型对象
例如:
function Person(){}
Person.prototype ={} // 等号右边的{}就是prototype指向的对象,就为原型对象
2.原型对象默认有一个叫做 constructor的属性,这个属性指向构造函数本身
person.prototype = {
constructor:Person
}
3.如果往原型对象上添加方法和属性,所有通过构造函数创建的实例,都共享原型对象上包含的属性和方法
function Star(name,age){
this.name =name
this.age = age
}
Star.prototype.sing = function(){
console.log('我会唱歌')
}
const ldh = new Star('刘德华',18)
const zxy = new Satr('张学友',22)
ldh.sing() //可以调用原型上的sing
zxy.sing() //可以调用原型上的sing
console.log(ldh.sing === zxy.sing) //true
1
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
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
# 26.proto
function Star(name, age) {
this.name = name
this.age = age
}
// 公共方法写在原型上
Star.prototype.sing = function () {
console.log('我会唱歌')
}
const ldh = new Star('刘德华', 18)
ldh.sing()
1.方法属性的查找规则
首先看ldh这个实例对象身上是否有sing方法,如果有,就执行对象上的方法
如果ldh实例对象本身没有sing这个方法,就会通过__proto__去实例的原型上查找
2.
实例通过__proto__访问(链接)到了它的原型对象
==>ldh.__proto__ ==>访问到了原型
==>Star.prototype ==>访问到了原型
ldh.__proto__ === Star.prototype
3.
__proto__ 本身就相当于一个桥梁,通过构造函数创建的实例对象可以通过它访问到原型对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
原型原理:

# 27.构造函数、实例、原型 三者之间的关系
1.构造函数有prototype属性,指向原型对象
2.原型对象默认有一个constructor属性,指向构造函数
3.构造函数 new 创建了一个实例
4.实例可以通过 __proto__访问的原型(对象)
1
2
3
4
2
3
4

# 28.constructor的应用
function Star(name, age) {
this.name = name;
this.age = age;
}
// 如果我们直接给原型对象赋值一个对象,相当于整个替换了原型对象里面的所有内容
// 原型对象里面的constructor就没有了,我们也不知道这个原型和哪个构造函数相关了
// 所以,可以手动的利用constructor指回原来的构造函数
Star.prototype = {
constructor: Star, //手动添加constructor
sing: function () {
console.log("sing");
},
dance: function () {
console.log("dance");
},
rap: function () {
console.log("rap");
},
};
console.dir(Star); // prototype里的 constructor就没有了
如果是单个给原型对象添加属性 就不会原型原型对象里constructor的存在
// Star.prototype.sing = function(){
// console.log('sing');
// }
// Star.prototype.dance = function(){
// console.log('dance');
// }
// Star.prototype.rap = function(){
// console.log('rap');
// }
// console.dir(Star); //prototype里的 constructor还有
1
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
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
# 29.原型对象的方法中的this指向
function Star(name){
this.name = name}
1.构造函数里面的this指向的是实例对象
// 2.原型对象的方法中 this指向 也是指向实例对象
Star.prototype.sing =function(){
console.log('唱歌');
console.log(this); //指向实例对象
}
ldh.sing()
zjl.sing()
// 构造函数自己的方法调用时 this指向构造函数本身
Star.sayHi(){
console.log(this) // Star
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 30.什么是原型?
// 原型就是一个对象,也叫做原型对象
// 1. 每个函数都有prototype属性,它的值是一个指针,指向的就是原型对象。
// 2. 通过构造函数生成的实例,都有一个__proto__属性,也指向原型对象。
// 3. 原型上默认有一个constructor属性,指回构造函数
// 原型的作用:
// 我们可以把一些公共的属性和方法放到原型上,
// 通过构造函数创建的实例,都共享原型上的属性和方法。
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 31.什么是原型链?

JS中一切皆对象
Person.prototype原型对象,也是一个对象,它也有__proto__属性
Person.prototype这个对象的构造函数为:Object
所有Person.prototype这个对象的隐式原型 指向 它的构造函数(Object)的显式原型
consolelog(Person.prototype.__proto__ === Object.prototype) //true
Object.prototype原型 默认也有一个constructor属性 ,这个属性指向构造函数Object
Object.prototype.constructor === Object
Object.prototype本身也是一个对象,也有__proto__属性
Object.prototype.__proto__ === null
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 32.原型链重要知识点
正常的原型链都会终止于Object的原型对象(或者说终止于null)
当访问一个对象的属性和方法时,先在自身寻找,
如果自身没有,就会沿着__proto__这条链,向上寻找,一直找到最顶层Object.prototype为止。
例如:
数组的原型链
const arr = [1,2,3] //arr = new Array(1,2,3)
arr --> Array.prototype --> Object.prototype --> null
函数的原型链
const fn = function(){} // fn == new Function()
fn --> Function.prototype --> Object.prototype --> null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 33.原型链继承

继承: 子类拥有父类的属性和方法
// 父类
function Parent(name){
this.name = name || '父类' //相当于给默认值
this.arr = [1,2,3]
}
Parent.prototype.sayHi = function(){
console.log('hi~~');
}
// 子类
function Child(hobby){
this.hobby = hobby
}
// 核心 ===> 让子类的原型 等于 父类的构造函数的实例
Child.prototype = new Parent() //相当于Child 和 Parent两个构造函数之间有了关联 // 因为没有传参 所以获得构造函数Parent的默认值: name:'父类' arr =[1,2,3]
Child.prototype.constructor = Child // 手动的修正Child的constructor // 子类原型与父类实例相等后, 子类原型中的constructor属性会丢失,所以手动补上
const boy1 = new Child('sing')
const boy2 = new Child('dance')
console.log(boy1,boy2);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
优点:方法复用,共享
因为方法定义在父类的原型上,通过原型链继承,子类实例共享了父类原型上的方法
boy1.sayHi()
boy2.sayHi()
1
2
3
4
2
3
4
缺点:
1.子类在实例化的时候,不能给父类的构造函数传参
2.子类的实例共享了父类构造函数的属性和方法
如果父类里的属性值是引用类型(数组,对象等),实例修改这个值后会互相影响
boy1.arr.push(666)
console.log(boy1.arr);// [1,2,3,66]
console.log(boy2.arr);// [1,2,3,66]
因为有上面两个缺陷,原型链继承一般不会单独使用!
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 33.数据类型
数据类型有哪些? // 易考面试题 背
ES变量有两种不同类型的数据:
1.原始值(基本数据类型):
Number String Boolean / undefined null / Symbol BigInt
2.引用值(引用数据类型)
Object ==> Function Array Date Math RegExp
基础数据类型 和 引用数据类型 在内存里是怎么存储的? //面试题 背
栈(Stack): 基本数据类型的值 存在栈里
堆(Heap): 引用类型的数据,栈里面存放的是地址,这个地址指向堆里的数据。
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 34.检测数据类型的方式
# 34.1 typeof
typeof 可以用来判断一个变量是否是除了null以外的基本数据类型
// 基本数据类型
console.log(typeof 1); // number
console.log(typeof ""); // string
console.log(typeof true); //boolean
console.log(typeof undefined); // undefined
console.log(typeof null); // object---有点儿特殊 Bug
console.log(typeof Symbol('id')) //symbol
console.log(typeof 9007199254740999n) // bigint
console.log(typeof BigInt(9007199254740999)) //bigint
// 2. typeof 总是返回一个字符串
console.log(typeof (typeof 1)) // string
console.log('-----------------------')
// 3. typeof 不能区分数组
console.log(typeof [1, 2, 3]) // 'object'
console.log(typeof function(){}) // 'function'
console.log(typeof {}) // 'object'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 34.2 instanceof
//instanceof 可以用于引用类型的检测。基本数据类型无效
// 基本类型 无效
console.log('1' instanceof String) // false
console.log(1 instanceof Number) //false
console.log(true instanceof Boolean) // false
console.log(Symbol('id') instanceof Symbol) //false
console.log(9007199254740999n instanceof BigInt) //false
console.log(BigInt(9007199254740999) instanceof BigInt) // false
// instanceof 只能用于引用类型的检测
console.log({} instanceof Object) // ture
console.log([] instanceof Object) // true
console.log(function (){} instanceof Function) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 34.3 Object.rptotype.toString.call
let num = 123
let str = '123'
let bool = true
let und = undefined
let nul = null
let symb = Symbol('id')
let big = 9007199254740999n
let arr = [1,2,3]
let obj = {}
let fn = function(){}
let date = new Date() // [object Date]
let reg = /a/g // [object RegExp]
let error = new Error() // [object Error]
// 前面的object小写, 后面的类型, 大写开头; 字符串
console.log(Object.prototype.toString.call(num)) // [object Number]
console.log(Object.prototype.toString.call(str)) // [object String]
console.log(Object.prototype.toString.call(bool)) // [object Boolean]
console.log(Object.prototype.toString.call(und)) // [object Undefined]
console.log(Object.prototype.toString.call(nul)) // [object Null]
console.log(Object.prototype.toString.call(symb)) // [object Symbol]
console.log(Object.prototype.toString.call(big)) // [object BigInt]
console.log(Object.prototype.toString.call(arr)) // [object Array]
console.log(Object.prototype.toString.call(obj)) // [object Object]
console.log(Object.prototype.toString.call(fn)) // [object Function]
console.log(Object.prototype.toString.call(date)) // [object Date]
console.log(Object.prototype.toString.call(reg)) // [object RegExp]
console.log(Object.prototype.toString.call(error)) // [object Error]
// 最完美的方式~~~
console.log('-----')
// 封装一个CheckType
const tempArr = [num, str, bool, und, nul, symb, big, arr, obj, fn, date, reg, error]
const checkType = (arr) => {
arr.forEach(el => console.log(Object.prototype.toString.call(el)))
}
checkType(tempArr)
1
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
35
36
37
38
39
40
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
35
36
37
38
39
40
# 35.判断数组 将伪数组转换为真数组、
// 判断 检测数组
/* ==================== ===================== */
// 1. Array.isArray()
// 2. A instanceof B
// 3. Object.prototype.toString.call()
/* ==================== ===================== */
// 将伪数组(类数组)转为真数组
/* ==================== ===================== */
// 1. Array.from()
// 2. 扩展运算符 [...arrayLike]
/* ==================== ===================== */
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 36.什么是浅拷贝 (面试题)

let obj1 = {
name:'JS',
book:{
title:"You Don't Know JS",
price:"169"
}
}
在堆内存空间中新开辟空间,创建一个新的对象
拷贝原对象的第一层基本数据类型的值 和 引用数据类型的地址
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 37.实现浅拷贝的方式有哪些?(面试题)
// 1. Object.assign()
// 2. 扩展运算符
// 3. 数组的concat()
// 4. 数组的slice()
例如:
let obj1 = {
name:'JS',
book:{
title:"You Don't Know JS",
price:"169"
}
}
// 1. Object.assign()
let obj2 = Object.assign({}, obj1)
// 第一层 name属性值 是基本数据类型,拷贝前后两个对象相互不影响
obj2.name = 'Hello World'
console.log(obj2)
console.log(obj1) // 如果obj1不受影响
// 第一层 如果属性值是引用类型,引用类型的值改变,会相互影响
obj2.book.price = '66'
console.log(obj2)
console.log(obj1)
// 2. 扩展运算符 spread ...
let c = {
name: "JS",
book: {
title: "You Don't Know JS",
price: "169",
},
};
let d = { ...c };
d.name = "贵桑";
d.book.price = "99";
// 3. Array.prototype.concat()
const arr1 = [1, 2, { name: "淞桑" }];
const arr2 = arr1.concat();
// console.log(arr2)
arr2[0] = 666;
arr2[2].name = "贵儿桑";
console.log(arr1);
console.log(arr2);
// 4. Array.prototype.slice()
const arr3 = [1, 2, { name: "杰伦" }];
const arr4 = arr3.slice();
arr4[0] = 888;
arr4[2].name = "萧萧";
console.log(arr3, arr4);
1
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 37.什么是深拷贝(面试题)

在堆内存中新开辟一个空间来存放新的对象,递归的拷贝原对象的所有属性和方法,拷贝前后两个对象,互不影响。
递归的拷贝: 即一层一层的拷贝,每一层都新建一个内存空间,存放新的对象。
1
2
3
2
3
# 38.实现深拷贝的方式
// 1. JSON.parse(JSON.stringify(obj))
// 2. 手写递归实现
// 3. 使用一些JS库,比如lodash等
1
2
3
4
2
3
4
# 39.深拷贝方式: JSON.parse(JSON.stringify(obj))存在的问题
const obj = {
name: 'zjl',
age: 18,
hobby:['dance','music'],
book:{
book_name:'Golang',
price:66
},
test1: function(){},
test2: undefined,
test3: Symbol('id'),
test4: new RegExp(/a/, 'g'),
test5: NaN,
test6: Infinity,
test7: new Date()
}
// JSON.parse() 将JSON格式的字符串转为对象
// JSON.stringify() 将对象转为JSON格式的字符串
const res = JSON.parse(JSON.stringify(obj))
console.log(res)
// 缺陷:
// 1. 拷贝对象的属性值如果是function / undefined / Symbol , 这个键值对丢失!
// 2. 如果拷贝对象的属性值是RegExp, 会变成{}
// 3. 如果NaN, Infinity 会变成null
// 4. 拷贝Date日期对象,会变成日期字符串
1
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
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
# 40.深拷贝 --- 手写递归(不完整版)
# 40.1 手写递归 (原对象属性中无数组和对象的情况)
const obj ={
name:'somi',
age:18
}
const o = {}
const deepClone =(newObj,oldObj) =>{
for(let k in oldObj){
newObj[k] = oldObj[k]
}
}
deepClone(o,obj)
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 40.2 手写递归(原对象属性中有数组的情况)
const obj ={
name:'some',
age:18,
hobby;['dance','music']
}
const o = {}
const deepClone =(newObj,oldObj) => {
for(let k in oldObj){
if(oldObj[k] instanceof Array){
newObj[k] = []
deepClone(newObj[k],oldObj[k])
} else{
newObj[k] = oldObj[k]
}
}
}
deepClone(o, obj)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 40.3 手写递归(原对象属性中有数组和对象的情况)
const obj ={
name:'somi',
age:18,
hobby:['dance','music'],
book:{
title:'Javascript',
price:66
}
}
const o = {}
const deepClone = (newObj, oldObj) =>{
for(let k in oldObj){
if(oldObj[k] instanceof Array){
newObj[k] = []
deepClone(newObj[k],oldObj[k])
} else if (oldObj[k] instanceof Object){
newObj[k] = {}
deepClone(newObj[k], oldObj[k])
} else {
newObj[k] = oldObj[k]
}
}
}
deepClone(o, obj)
//注意点: 当原对象属性中既有数组又有对象时, 需要先判断是否是数组,再判断是否是对象,因为数组也是属于Object。
1
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
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
# 41. 深拷贝-- 引入lodash库 (最简便的方式)
<script src='./lodash.min.js'></script>
<script>
const obj ={
name:'somi',
age:18,
hobby:['dance','music'],
book:{
title:'Javascript',
price:66
}
}
// lodash库已经封装好了递归函数,直接引用就行
//_.cloneDeep() 名字不能修改 _ 表示lodash
const o = _.cloneDeeP(obj)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 42.throw抛出异常
function fn(x,y){
if(!x || !y){
throw new Error('没有参数传递过来')
//throw 可以抛出错误,并且它会中断代码的指向
console.log(123)
}
return x + y
}
console.log(fn())
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 43. try-catch-finally 捕获异常
<p>123</P>
<script>
function fn(){
try{
// 将可能发生错误的代码写在try模块里,如果代码发生错误,会终止执行try里错误代码之后的代码,进而执行catch板块里的错误消息提示代码
const p = document.querySelector('.p')
console.log(p) // null
p.style.color = 'orange' //这里报错了,这里的报错信息会体现在catch代码中
console.log(666) //因为前面的代码发生错误,所以此处代码不会执行,不会打印
} catch(error){
console.log(error.message) // Cannot read properties of null (reading 'style')
//try-catch 可以拦截捕获错误,但是不会中断后面代码的执行
//如果要中断后面的代码执行
//1.return 不会中断finally代码执行
//2.throw new Error('msg') 不会中断finally代码执行
}
finally{
//不管前面代码对不对或者前面有return/throw抛出错误,都会执行这里的代码
console,log('会执行')
}
console.log(999)
}
fn()
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 44. this指向(复习)
//this指向在定义的时候不能确定,只有在调用执行的时候才能确定!
//1. 全局作用域 / 普通函数调用 /定时器 this指向的是window
console.log(this) // window
function fn(){
console.log(this) // window
}
fn()
setTimeout(function(){
console.log(this) // window
},1000)
//2. 对象方法调用中,谁调用这个方法,this就指向谁
const obj ={
name:'Amy',
age:6,
sayHi:function(){
console.log(this)
}
}
obj.sayHi() // obj{}
let fun = obj.sayHi
fun() // window
//3.事件注册的时候,this指向被绑定的元素
const btn = document.querySelector('button')
btn.addEventListener('click',function(e){
console.log(this) // 绑定事件的元素
console.log(e.target) // 触发事件的元素
console.log(e.currentTarget) //同this相同,绑定事件的元素
})
//4.构造函数中的this指向 实例对象
function Parent(name,age){
this.name = name
this.age = age
console.log(this) // p:('灰灰',9), p1:('超超',19)
}
const p = new Parent('灰灰',9)
const p2 = new Parent('超超',19)
//5.原型方法里面,this指向的是实例对象
Parent.prototype.sayHi = function(){
console.log('hi')
console.log(this) // p:('灰灰',9)
}
p.sayHi()
1
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 45.箭头函数的特殊之处
//1.箭头函数没有prototype属性,也没有原型对象
let a = () => {}
console.log(a.prototype) // undefined
//2. 不能使用new 调用箭头函数,即箭头函数不能作为构造函数
let B = () => {}
let c = new B() // 报错:B is not a constructor
console.log(c) // 上面的代码报错,不会执行此处代码
//3. 箭头函数没有arguments对象, 利用剩余参数rest接收
let d = (first,...abc) => {
//console.log(arguments) // 报错 Unexpected identifier 'Error'
console.log(first,abc) //1 (3) [2, 3, 4]
}
d(1,2,3,4)
//4. 箭头函数本身没有this
//箭头函数的this指向在定义的时候就确定了,指向的是上层作用域中的this
let msg = '666' // 注意!!! let / const 声明的变量不会挂载在window上
let obj = {
msg:'777',
sayHi:function(){
console.log('sayHi',this.msg)
},
sayHello:() =>{
//对象不能形成作用域,所以此处的this指向的是window
console.log('sayHello',this.msg)
}
}
obj.sayHi() // sayHi 777 普通函数作为对象的方法调用时,指向对象
obj.sayHello() //sayHello undefined //let / const 声明的变量不会挂载在window上
1
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
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
# 46.改变this指向的方法
# 46.1 fn.call()
//1. 可以改变this的指向
//2. 调用函数,立即执行,返回执行结果
//3. fn.call()第一个参数就是让this指向哪个对象,后面紧跟的是参数列表
//eg1
const obj = {
msg:"hello world"
}
function fn(x, y){
console.log(this)
return x + y
}
const res = fn(1, 2) //调用fn函数 打印this : window
console.log(res) // 3
const res2 = fn.call(obj, 1, 2) //调用fn函数 打印this 改变this指向 : obj
console.log(res2) //3
//eg2
const obj2 = {
name:'hw',
getName(){
console.log(this)
console.log(this.name)
}
}
const obj3 = {
name:'iphone'
}
//改变getName里面的this指向,让它指向obj3
obj2.getName.call(obj3) //可以不接受后面的参数 {name: 'iphone'} iphone
1
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
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
# 46.2 fn.apply()
//1. 改变this指向
//2. 调用函数,立即执行,返回执行结果
//3. 第一个参数是this指向的对象,第二个参数是数组
const obj = {
msg:"hello world"
}
function fn(x, y){
console.log(this)
return x + y
}
const res = fn(1,2) // window
console.log(res) // 3
const res2 = fn.apply(obj,[1,2]) // obj
console.log(res2) // 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 46.3 fn.bind()
//1. 改变this指向
//2. bind的返回值是一个函数,这个函数里面的this就是传入的第一个参数
//3. bind不是立即执行,需要手动调用
const obj = {
msg:"hello world"
}
function fn(x, y){
console.log(this)
return x + y
}
const fun = fn.bind(obj,1,2) //返回改变this指向后的fn函数 不会执行
console.log(fun) //ƒ fn(x, y) {
//console.log(this)
//return x + y
//}
//手动调用执行函数
const res = fun() // obj
console.log(res) // 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 46.4 fn.call() / fn.apply() / fn.bind()的区别(面试题)⭐
1.都是改变this的指向
2.call接受的是参数列表, apply接受的是数组
3.call 和 apply是立即执行的,bind返回的是一个函数,需要手动调用
1
2
3
2
3
# 47. 防抖: debounce
//防抖的含义
//当持续触发事件时,一定时间内没有再触发事件,回调函数才会执行一次
//如果设定的延迟时间到来之前,又触发了事件,就重新执行。
//防抖的应用场景
//1. 搜索框输入查询的时候
//2. 鼠标连续点击按钮提交等
<input type="text">
<script>
const input = document.querySelector('input')
const sendMsg = function(){
console.log('发送请求')
}
const debounce =(fn, ms = 0) =>{
let timerId
return function(){
clearTimeout(timerId)
timerId = setTimeout(() =>{
fn.call(this)
},ms)
}
}
input.addEventListener('keyup',debounce(sendMsg,500))
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 48. 防抖debounce传参
<input type="text">
<script>
const input = document.querySelector('input')
const sendMsg = function(){
console.log('发送请求')
}
const debounce =(fn, ms = 0) =>{
let timerId
return function(...args){
clearTimeout(timerId)
timerId = setTimeout(() =>{
fn.call(this,...args)
//fn.apply(this,args)
},ms)
}
}
input.addEventListener('keyup',debounce(sendMsg,500))
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 49. 定时器中this指向
function foo(){
console.log(this, 'foo')
}
const obj = {
name:'test',
fn:function(){
console.log(this, '---定时器外层的this') // obj
setTimeout(function(){
console.log(this, 'function') // window
// 在定时器的回调函数中,又调用了一个函数
// foo调用执行的时候,它里面的this和定时器是不是箭头函数没有关系
foo() //widow
}, 1000);
setTimeout(() => {
console.log(this, '箭头函数') // obj
foo() // window
}, 2000)
}
}
obj.fn()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 50. 节流:throttle
# 50.1 节流的含义和应用场景
//节流:throttle 含义
// 减少一段时间内,事件的触发频率
//应用场景
//1.浏览器窗口缩放 : resize事件
//2.scroll滚动事件 / mousemove事件等
1
2
3
4
5
6
7
2
3
4
5
6
7
# 50.2 节流 -- 定时器版本
<script>
const box = document.querySelector('.box')
let i = 1
const move = () => {
// 只要鼠标一移动,i就会变化
box.innerHTML = i++
}
//需求:每隔100ms只执行一次
//整个程序 ,应该只有一个定时器在走
const throttle = (fn, ms = 0){
let timerId
return function(){
if(!timerId){
timerId = setTimeout(() => {
// 箭头函数没有arguments对象,找的是上层函数中的arguments
fn.apply(this,arguments)
//fn.apply(this,args)
timerId = null // undefined // 0
},ms)
}
}
}
box.addEventListener('mousemove',throttle(move,100))
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 50.3 节流 -- 时间戳的版本
<script>
const box = document.querySelector('.box')
let i = 1
const move = () => {
// 只要鼠标一移动,i就会变化
box.innerHTML = i++
}
//需求:每隔100ms只执行一次
const throttle = (fn,ms) =>{
let pre = 0 // 记录一个开始时间
return function(...args){
let now = + new Date()
if(now - pre >= ms){
fn.apply(this,args)
// 当我们执行完一次之后,可以把当前时间,作为下一次计算的开始时间
pre = Date.now()
}
}
}
box.addEventListener('mousemove',throttle(move,100))
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 51. 引入lodash库 节流防抖
<div class="box"></div>
<script src="./lodash.min.js"></script>
<script>
const box = document.querySelector('.box')
let i = 1
const move = () => {
box.innerHTML = i++
}
// 节流 _.throttle(fn, ms)
// 防抖 _.debounce(fn, ms)
// box.addEventListener('mousemove', _.throttle(move, 10))
box.addEventListener('mousemove', _.debounce(move, 300))
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 52.ES6 类Class
# 52.1 利用类创建一个对象
class Star{
constructor(name,age){
this.name = name
this.age =age
}
}
// new 一个实例对象
const hx = new Star('Push',19)
// 注意点
// 构造函数和类同名了,不能重复声明
// 1. constructor() 是类的默认方法,只要通过new 创建一个实例,就会执行里面的代码
// 2. 一个类里面必须要有constructor()方法,如果不写,一个空的constructor会默认添加
// 3. constructor可以接收传递过来的参数,默认返回实例对象(this)
// 4. 类必须使用 new 来调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 52.2 类添加公共方法
class Star {
// 1. 公共的属性写在constructor里面
constructor(name, age){
this.name = name
this.age = age
}
// 2. 类里面可以直接写方法,方法不需要加function关键字
sing(song){
console.log(song)
}
// 3. 多个方法之间不需要逗号隔开
dance(){
console.log('跳舞')
}
}
const ldh = new Star('刘德华', 19)
console.log(ldh)
ldh.sing('冰雨')
console.log(ldh.__proto__ === Star.prototype)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 52.3 类里面的this指向
class Star {
// 1. constructor 里面的this 指向的是创建 new 的实例对象
constructor(name, age){
this.name = name
this.age = age
console.log(this) // Star {name: '航桑', age: 19}
}
// 2. 类的方法内部,如果含有this, 它默认指向的是类的实例
sing(song){
console.log(song)
console.log(this) //Star {name: '航桑', age: 19}
}
dance(){
console.log('跳舞')
}
}
const handsome = new Star('航桑', 19)
handsome.sing('冰雨')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 52.4 使用类的注意事项
// 1. ES6中 类 没有变量提升,所以必须先声明类,再通过类实例化对象。
// const ldh = new Star('刘德华', 18) //报错 Cannot access 'Star' before initialization
class Star {
constructor(name, age){
this.name = name
this.age = age
// console.log(this)
// sing()
this.sing()
}
// 2. 公共的属性和方法,在类里面使用的时候,前面一定要记得加this
sing(){
console.log('唱冷冷的冰雨')
console.log(this.name)
}
dance(){
console.log('跳舞')
}
}
const ldh = new Star('刘德华', 18)
// 想要new Star 的时候,就去调用执行sing方法
// ldh.sing()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 52.5 类的本质
class Star {
}
console.log(typeof Star) // function
// 1. 类的本质是一个函数,我们可以简单的认为,类就是构造函数的另一种写法。
// 2. 类有原型
console.log(Star.prototype) // obj
// 3. 我们也可以直接在类的原型上添加方法(不推荐),公共方法一般写在类里面
Star.prototype.sing = function(){
console.log('唱歌')
}
console.log(Star.prototype)
// 4. 通过类创建的实例,有__proto__属性,可以访问到原型
const ldh = new Star()
console.log(ldh.__proto__)
console.log(ldh.__proto__ === Star.prototype)
console.log(Star.prototype.constructor === Star)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 52.6 类的继承
// 寄生式组合继承
// ===> extends ES6 类的继承 完美的 perfect~~~
// 继承: 让子类拥有父类的属性和方法
// 定义一个父类
class Animal {
constructor(name, age){
this.name = name
this.age = age
}
run(){
console.log('奔跑')
}
}
// 写一个子类Dog 继承 Animal这个父类
class Dog extends Animal {
// 子类里面没有写属性和方法,但是 通过extends,直接继承了父类的name和age
// 子类 创建的实例,可以调用父类的方法 (继承父类的属性和方法)
}
const dog = new Dog('西瓜', 3) //
console.log(dog)
dog.run() //奔跑
1
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
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
# 53. super关键字
// 定义一个父类
class Father {
constructor(){}
}
// 定义一个son继承Father
class Son extends Father {
constructor(){
super()
}
}
// 1. 子类实现继承的时候必须在constructor方法里面,调用super(), 否则会报错。
// ===> 如果子类里面写了constructor() ,就必须要写super()
const p = new Son()
// 2. 如果子类没有写constructor(), 这个方法会默认添加,并且里面还会默认调用super()
class Son extends Father {
}
// 等同于
class Son extends Father {
constructor(){
super()
}
}
// 正常情况下,我们都会在子类里写constructor,并且写super()
1
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
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
# 53.1 super关键字注意事项
// 定义父类
class Father {
// 这里的constructor叫做构造函数
constructor(x, y) {
this.x = x
this.y = y
console.log(this)
}
// 求和
sum(){
console.log(this.x + this.y)
}
}
// 定义一个子类,继承父类
class Son extends Father {
// 1. super() 必须在子类的this前面调用
// 2. super() 代表了父类的构造函数
// 3. super() 里面的this 指向的是谁呢?
// ===> 指向的是子类的实例,Son的实例 p这个对象
constructor(x, y, color){
super(x, y) // ==> super(x, y) 父类的constructor(x, y)
// 如果在这里又写this.x = x
// super这个方法后面加的属性,是子类自己的一些属性了
// this.x = x + 2
// this.y = y + 2
// this.color = color
// super.sum()
}
minus(){
console.log(this.x - this.y)
}
// 4. 怎么理解super()要在this前面调用?
// 我们现在是不是在实现继承,让子类拥有父类的属性和方法
// 我们需要先继承父类的属性和方法,然后再扩展自己的属性和方法。
}
const p = new Son(5, 3, 'orange')
// console.log(p)
// p.sum()
// p.minus()
// 1. 子类不能往父类构造函数传参, 通过super() 就可以往父类构造函数传参
1
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
35
36
37
38
39
40
41
42
43
44
45
46
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
35
36
37
38
39
40
41
42
43
44
45
46