黑马程序员JavaScript语法进阶
javascript进阶——对之前的语法进行补充和优化
JavaScript 进阶 - 第1天

学习作用域、变量提升、闭包等语言特征,加深对 JavaScript 的理解,掌握变量赋值、函数声明的简洁语法,降低代码的冗余度。
- 理解作用域对程序执行的影响
- 能够分析程序执行的作用域范围
- 理解闭包本质,利用闭包创建隔离作用域
- 了解什么变量提升及函数提升
- 掌握箭头函数、解析剩余参数等简洁语法
作用域
了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域和局部作用域。
局部作用域
局部作用域分为函数作用域和块作用域。
函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
<script> |
总结:
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量实际被清空了
块作用域
在 JavaScript 中使用 {} 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
<script> |
JavaScript 中除了变量外还有常量,常量与变量本质的区别是【常量必须要有值且不允许被重新赋值】,常量值为对象时其属性和方法允许重新赋值。
总结:
let声明的变量会产生块作用域,var不会产生块作用域const声明的常量也会产生块作用域- 不同代码块之间的变量无法互相访问
- 推荐使用
let或const
注:开发中 let 和 const 经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用 const 声明成常量。
全局作用域
<script> 标签和 .js 文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
<script> |
全局作用域中声明的变量,任何其它作用域都可以被访问,如下代码所示:
<script> |
总结:
- 为
window对象动态添加的属性默认也是全局的,不推荐!window.i = 1 - 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误。
作用域链
在解释什么是作用域链前先来看一段代码:
<script> |
函数内部允许创建新的函数,f 函数内部创建的新函数 g,会产生新的函数作用域,由此可知作用域产生了嵌套的关系。
如下图所示,父子关系的作用域关联在一起形成了链状的结构,作用域链的名字也由此而来。
作用域链(可以说冒泡、就近)
作用域链本质上是底层的变量查找机制
在函数被执行时,会优先查找当前函数作用域中查找变量,
如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域,如下代码所示:
<script> |
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
就像作用域像链子一样连接,从小到大的查找规则!
内存结构
B站视频:深入理解javascript内存管理0:22,参考文章:js内存分配机制
一、数据存储
简单数据类型:存储在栈里面,是操作系统帮我们管理内存空间,执行时,才会分配空间
引用数据类型:存储在堆里面,由程序员控制大小(管理内存空间)
二、变量生命周期
函数在执行时,简单数据存储在栈中,复杂类型存储在堆中,执行完就释放空间!
分三步走:分配空间 => 使用空间 => 释放空间
分配: 在js中,申明变量、函数、对象的时候,系统会⾃动为他们分配内存
使⽤: 使⽤变量、函数等,即读写内存
回收: 垃圾回收机制⾃动回收不再使⽤的内存
分配内存:

根据系统位数,分配16M/32M内存,再拆成form空间和to空间,称为新生代,一开始读取都是放在form,把碎片化的空间整理后,再复制一份存放在to空间!

历经多次释放后,生命周期较长的会单独分到老年态,像闭包分配的空间会跑到老年态里!
大于内存页的分配到大对象空间
热门函数,编译成机器代码,放到代码空间
每种内存都有垃圾回收机制!针对的是堆空间的内存!
垃圾回收机制(GC)
JS中内存的分配和回收,都是自动完成的,内存在不使用的时候,会被垃圾回收器自动回收!
内存的生命周期
JS环境中分配的内存,一般有如下生命周期(三个阶段):
- 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
- 内存使用:即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存
说明:怎么判断使用完毕呢?
全局变量一般不会回收(除非关闭页面,全部都回收)
一般情况下局部变量的值,不用了,就会自动回收掉!就是读写完毕后!
内存泄漏
程序中分配的内存由于某种原因,程序未释放或无法释放叫做内存泄漏!
JS垃圾回收机制-算法说明
堆栈空间分配区别:
- 栈(操作系统): 由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
- 堆(操作系统): 一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。
下面介绍两种常见的浏览器垃圾回收算法: 引用计数法 和 标记清除法
引用计数
不再使用的对象:就是看⼀个对象是否有指向它的引⽤, 如果没有其他对象指向它了,说明该对象已经不再需要了。
但如果两个对象相互引⽤(即循环引用),尽管他们已不再使⽤,垃圾回收不会进⾏回收,导致内存泄露


- 跟踪记录被引用的次数
- 如果被引用了一次,那么就记录次数1,多次引用会累加 ++
- 如果减少一个引用就减1 –
- 如果引用次数是0 ,则释放内存

函数调用时,分配内存空间,那函数声明时,分配内存空间吗?
标记清除算法
标记清除算法将“不再使⽤的对象”定义为“⽆法达到的对象”:就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。
凡是能从根部到达的对象,都是还需要使⽤的。 那些⽆法由根部出发触及到的对象被标记为不再使⽤,稍后进⾏回收。
- 垃圾收集器在运⾏的时候会给存储在内存中的所有变量都加上标记。
- 从根部(global)出发将能触及到的对象的标记清除。
- 那些还存在标记的变量被视为准备删除的变量。
- 最后垃圾收集器会执⾏最后⼀步内存清除的⼯作,销毁那些带标记的值并回收它们所占⽤的内存空间。
内存泄漏
1、全局变量
function fn() { |
2、未被清理的定时器、回调函数等
var resp = getRich(); |
注意:
如果后续 app 元素被移除,整个定时器实际上没有任何作⽤。
但如果没有回收定时器,整个定时器依然有效, 不但定时器⽆法被内存回收,定时器函数中的依赖也⽆法回收。
在上述案例中的 resp 也⽆法被回收。
3、闭包
在 JS 中,⼀个内部函数,有权访问包含其的外部函数中的变量(闭包)如下情况:闭包也会造成内存泄露
4、DOM引⽤
很多时候, 我们对 Dom 的操作, 会把 Dom node 的引⽤保存在⼀个数组或者 Map 中。
var elements = { |
注意:即使我们通过操作 dom 把image元素给移除了,但是仍然有对 该image元素 的引用,仍然无法对其进行内存回收。
如何避免内存泄露
1、减少不必要的全局变量,可使⽤严格模式避免意外创建全局变量。
2、在使⽤完数据后,及时解除引⽤:赋值null(闭包中的变量,dom引⽤,定时器清除)。
3、组织好代码逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。
基本类型所占Bytes:
number: 8字节
string: 每个长度 2 字节
boolean: 4 字节
闭包——是一种技巧
利用内存泄漏+作用域链,让外部访问函数内部私有变量,即内部变量可以不被回收,并外部函数访问
闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个做为返回值的函数,如下代码所示:
<body> |
总结:
1.怎么理解闭包?
- 闭包 = 内层函数 + 外层函数的变量
注意:函数要调用才会被执行,才分配内存?

2.闭包的作用?
- 封闭数据,实现数据私有,外部也可以访问函数内部的变量
- 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
// 闭包的基本格式 |
需求:打印函数调用的次数

function fn() { |
3.闭包可能引起的问题?
- 内存泄漏
变量提升(了解var)
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问,即当代码在执行之前,var声明的变量提升到当前作用域的最前面!只提升声明,不提升赋值!
<script> |
总结:
- let、const变量在未声明即被访问时会报语法错误
- var变量在声明之前即被访问,变量的值为
undefined let声明的变量不存在变量提升,推荐使用let- var变量提升出现在相同作用域当中,局部就在局部内前,全局下依旧不能用,会报错!
- 实际开发中推荐先声明再访问变量
注:关于变量提升的原理分析会涉及较为复杂的词法分析等知识,而开发中使用 let 可以轻松规避变量的提升,因此在此不做过多的探讨,有兴趣可查阅资料。
函数
知道函数参数默认值、动态参数、剩余参数的使用细节,提升函数应用的灵活度,知道箭头函数的语法及与普通函数的差异。
函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。即会把所有函数声明提升到当前作用域的最前面!只提升声明,当调用时随意写!
<script> |
总结:
- 函数提升能够使函数的声明调用更灵活
- 函数表达式不存在提升的现象
- 函数提升出现在相同作用域当中
函数参数
函数参数的使用细节,能够提升函数应用的灵活度。除了形参和实参,形参可以赋予默认值,函数还有动态参数、剩余参数!
默认值
<script> |
总结:
- 声明函数时为形参赋值即为参数的默认值
- 如果参数未自定义默认值时,参数的默认值为
undefined - 调用函数时没有传入对应实参时,参数的默认值被当做实参传入
动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。仅存在于函数中!
<script> |
总结:
arguments是一个伪数组,有索引和值,没有常用方法,仅几个个性化方法!arguments的作用是动态获取函数的实参- 可以使用for遍历,在该函数中获取传入的实参!
剩余参数(可用于函数和解构中)
剩余,就是传递的多余的实参,只能接受剩余的参数!(a,b,…other),按顺序传入实参,第二位的后面
<script> |
总结:
...是语法符号,置于最末函数形参之前,用于获取多余的实参- 借助
...获取的剩余实参,是个真数组
…arr与arguments相比,更加灵活!能使用数组的方法!提倡使用剩余参数!特别是箭头函数!
与展开运算符的区别(…)
剩余参数在函数中使用,访问时不加(…),仅在函数形参写成这样!展开运算符是任何地点都可以使用,并且使用时(…arr)!
- 展开运算符
...,将一个数组进行展开 - 并不会修改原数组
- 最典型,求最大值、Math.max(…arr)。合并数组
arr = [...arr1,...arrr2]

箭头函数
箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。并且不绑定this!主要使用场景是需要匿名函数的地方!可以平替匿名函数!
普通函数更强调代码的复用,而箭头函数,多用于函数表达式,或者回调函数中!实现函数的简写!函数越简单,箭头函数越简洁,越好用!

语法特点
语法1:基本写法() => {}

语法2:只有一个参数可以省略小括号
语法3:如果函数体只有一行代码,可以写到一行上,并且无需写 return 直接返回值
语法4:加括号的函数体,返回对象字面量表达式
<body> |
总结:
- 箭头函数属于表达式函数,因此不存在函数提升
- 箭头函数只有一个参数时可以省略圆括号
() - 箭头函数函数体只有一行代码时可以省略花括号
{},并自动做为返回值被返回
箭头函数参数
箭头函数中没有 arguments,只能使用 ... 动态获取实参
<body> |
箭头函数 this
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
<script> |
对象中的箭头函数
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。即把这个this放到上一层去判断!在上一层新建一个this试试!

事件中的箭头函数
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window,因此 如果在DOM事件回调函数为了简便使用this,还是不太推荐使用箭头函数!

解构赋值
知道解构的语法及分类,使用解构简洁语法快速为变量赋值。
解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值,分为数组解构、对象解构两大类型。
数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法

如下代码所示:
<script> |
总结:[] = arr
- 赋值运算符
=的 左侧[],用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量 - 变量的顺序对应数组单元值的位置依次进行赋值操作
- 变量的数量大于单元值数量时,多余的变量将被赋值为
undefined - 变量的数量小于单元值数量时,可以通过
...获取剩余单元值,但只能置于最末位

- 允许初始化变量的默认值,且只有单元值为
undefined时默认值才会生效,类似参数传递!

- 按需导入赋值

注:支持多维解构赋值,比较复杂后续有应用需求时再进一步分析

注意: js 前面必须加分号情况
数组作为开头时,编码器易解析成前面代码的一部分,加上结束的分号就能正常识别了!

比如在交换值时:写在;[]

总之:

对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法,由于无序,根据属性名解构!

如下代码所示:
<script> |
总结:
- 赋值运算符
=左侧的{}用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量 - 对象属性的值将被赋值给与属性名相同的变量
- 对象中找不到与变量名一致的属性时变量值为
undefined - 允许初始化变量的默认值,属性不存在或单元值为
undefined时默认值才会生效
注:支持多维解构赋值
对象解构的更改变量名
{:} = {},可以从一个对象中提取变量并同时修改新的变量名
const {name: username, age} = {name: '小明', age: 18} |
冒号表示“什么值:赋值给谁”
数组对象解构:

多级对象解构

解构JSON对象
// 1. 这是后台传递过来的数据 |
综合案例
forEach遍历数组
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
注意:
1.forEach 主要是遍历数组
2.参数当前数组元素是必须要写的, 索引号可选。
<body> |
filter筛选数组
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
主要使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组
<body> |
综合案例:筛选价格
// 1. 渲染函数 |
小结:
- 箭头函数、解构、能让代码简洁,但功能思路还是差不多
- 知道全局变量占内存后,或许会写更多函数,或者代码块,来优化
- 这个关键简洁还是筛选数组的方法,不然的话,需要设置三个数组,根据价格筛选对象进行追加
- 解构对于大坨数据,仅需要其中一部分非常好用
- 箭头函数对于一句话返回真简洁
- 遍历对象数组追加字符串,对于渲染页面来说,真方便,除了有点长,但反正不用管内部细节!
- 但是需要CSS配合,不然的话,生成的HTML结构还是太骨感了!
总结:
- 学习了闭包,闭包形成的前提是作用域链和垃圾回收机制无法回收,也就是内存泄漏!
- 箭头函数,简洁性,与匿名函数的绝配(不需要this的匿名函数)
- (解构对于摘葡萄),对象中万千数据,只取其中几个!
JavaScript 进阶 - 第2天

了解面向对象编程的基础概念及构造函数的作用,体会 JavaScript 一切皆对象的语言特征,掌握常见的对象属性和方法的使用。
- 了解面向对象编程中的一般概念
- 能够基于构造函数创建对象
- 理解 JavaScript 中一切皆对象的语言特征
- 理解引用对象类型值存储的的特征
- 掌握包装类型对象常见方法的使用
深入对象
了解面向对象的基础概念,能够利用构造函数创建对象。
创建对象三种方式
利用对象字面量创建对象
利用 new Object 创建对象

- 利用 new Object 创建对象
构造函数
构造函数
是一种特殊的函数,主要用来初始化对象,不要搞混构造函数和对象!构造函数是函数,需要先声明后调用
构造函数是专门用于创建对象的函数,如果一个函数使用 new 关键字调用,那么这个函数就是构造函数。
使用场景
常规的 {…} 语法允许创建一个对象。比如我们创建了佩奇的对象,继续创建乔治的对象还需要重新写一 遍,此时可以通过构造函数来快速创建多个类似的对象

使用构成函数
不过有两个约定: 1. 它们的命名以大写字母开头。 2. 它们只能由 “new“ 操作符来执行。
<script> |
总结:
- 使用
new关键字调用函数的行为被称为实例化 - 实例化构造函数时没有参数时可以省略
() - 构造函数的返回值即为新创建的对象
- 构造函数内部的
return返回的值无效!
注:实践中为了从视觉上区分构造函数和普通函数,习惯将构造函数的首字母大写。
实例化执行过程——new过程4步
为什么一个函数,可以生成对象,也就是实例化,这与普通函数的调用不同,通过new关键字,生成对象!
- 创建新的空对象
- 构造函数this指向新对象
- 执行构造函数代码,修改this,添加新的属性
- 返回新对象


注意:1和2是new实例化的特点,3是函数与对象的特点,4是函数的特点!
实例成员
通过构造函数创建的对象称为实例对象,实例对象中的属性(实例属性)和方法(实例属性)称为实例成员
<script> |
总结:
- 构造函数内部
this实际上就是实例对象,为其动态添加的属性和方法即为实例成员 - 为构造函数传入参数,动态创建结构相同但值不同的对象
注:构造函数创建的实例对象彼此独立互不影响。
静态成员
在 JavaScript 中底层函数本质上也是对象类型,因此允许直接为函数动态添加属性或方法,构造函数的属性和方法被称为静态成员。——函数可以添加属性和方法,数组可以添加属性和方法吗?属性和类似变量!
只能通过外部添加的方式,先声明一个函数,然后再为其添加属性和方法!毕竟是个函数,不能像对象一样,在内部直接添加属性,如果直接添加属性,这样是无法引用的!从作用域讲,外部无法访问函数内部值,并且函数无返回任何值,所以是不能添加在内部!只能在外部,这样可以当作对象!添加属性值!
<script> |
总结:
- 静态成员指的是添加到构造函数本身的属性和方法,静态成员只能构造函数来访问!!!
- 一般公共特征的属性或方法静态成员设置为静态成员
- 静态成员方法中的
this指向构造函数本身
注意:像之前使用Math.PI就是静态属性,Math.floor()就是静态方法,还有Date.now(),直接使用!
内置构造函数
掌握各引用类型和包装类型对象属性和方法的使用。
在 JavaScript 中最主要的数据类型有 6 种:(保存在容器中的值!)
- 基本数据类型:
分别是字符串、数值、布尔、undefined、null
- 引用类型:
对象,常见的对象类型数据包括数组和普通对象。
基本包装类型
除了对象类型,有内置的属性和方法,甚至字符串、数值简单数据类型也有属性和方法,说明并不简单,JS从底层对简单数据类型做了一层包装,它们都有专门的构造函数,用于创建对应类型的数据!

非常好用的属性和方法——处理数据!
在 JavaScript 内置了一些构造函数,绝大部的数据处理都是基于这些构造函数实现的,JavaScript 基础阶段学习的 Date() 就是内置的构造函数。
<script> |
内置构造函数分类
引用类型:Object,Array,RegExp,Date 等
包装类型:String,Number,Boolean 等
Object
Object 是内置的构造函数,用于创建普通对象。推荐使用字面量!
<script> |
学下三个常用的静态方法:就是只能由构造函数Object调用的!
Object.keys()
获取对象的所有属性名(键),不需要for (let k in obj){},传入对象,以数组的形式返回对象的键!
Googs.vivo = '2299' |
Object.values()
传入对象,以数组的形式返回对象的属性值!
Object.assign(),拷贝对象,或者追加对象

总结:
- 推荐使用字面量方式声明对象,而不是
Object构造函数 Object.assign()静态方法创建新的对象Object.keys()静态方法获取对象中所有属性Object.values()表态方法获取对象中所有属性值
Array
Array 是内置的构造函数,用于创建数组。
<script> |
数组赋值后,无论修改哪个变量另一个对象的数据值也会相当发生改变。


reduce(函数(上一次值, 当前值), 起始值)——求和
reduce() 返回函数累计处理的结果,经常用于求和等,起始值可以省略,如果写就作为第一次累计的起始值

// 有初始值时,多加对应的值,没有初始值,就只累加数组内的值,记住用return返回!箭头一句省略 |
- 如果没有起始值,则【上一次值】是,数组的第一个元素的值,如果有,则起始值作为【上一次值】
- 每次循环,把返回值作为下一次循环中【上一次值】,把数组中下一次元素当作【当前值】
没有初始值:

有初始值:

案例:算工资
arr = [{ |
分析,这是一个数组,但又不是单纯的数组!
方法一是把这个对象数组的salary全部拿出来,放到一个数组中
方法二更妙,依旧把这个当作数组,当遍历其中的对象时,加个点操作符,取对象中salary的值!

注意:这里为了让遍历次数与元素(对象)个数一致,添加个0作为初始值!
数组:实例方法汇总——MDN手册
- 推荐使用字面量方式声明数组,而不是
Array构造函数 - 实例方法
forEach用于遍历数组,替代for循环 (重点) - 实例方法
filter过滤数组单元值,生成新数组(重点) - 实例方法
map迭代原数组,生成新数组(重点) - 实例方法
join数组元素拼接为字符串,返回字符串(重点) - 实例方法
find查找元素, 返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined(重点)

注意:对于普通函数来讲,这个方法的用处不大,只能通过元素查找并返回查找的元素!但是对于对象数组来讲,可以通过对象中某一个属性的值,来查找对象,并返回查找的对象,这就具有很大价值了!
实例方法
every检测数组所有元素是否都符合指定条件,如果所有元素都通过检测返回 true,否则返回 false(重点)——类似filter(),不过返回的是否满足,是布尔值!实例方法
some检测数组中的元素是否满足指定条件 如果数组中有元素满足条件返回 true,否则返回 false实例方法
concat合并两个数组,返回生成新数组实例方法
sort对原数组单元值排序实例方法
splice删除或替换原数组单元实例方法
reverse反转数组实例方法
findIndex查找元素的索引值
案例:

// 通过静态方法,获取对象的属性值,再使用join方法,拼接成字符串! |
静态方法
Array.from()——伪数组转化为真数组
包装类型
在 JavaScript 中的字符串、数值、布尔具有对象的使用特征,如具有属性和方法,如下代码举例:
<script> |
之所以具有对象特征的原因是字符串、数值、布尔类型数据是 JavaScript 底层使用 Object 构造函数“包装”来的,被称为包装类型。
String
String 是内置的构造函数,用于创建字符串。
<script> |
字符串:实例属性和方法汇总——MDN手册
- 实例属性
length用来获取字符串的度长(重点) - 实例方法
split('分隔符')用来将字符串拆分成数组(重点)——与join()相反——一般通过字符来拆分!

实例方法
substring(需要截取的第一个字符的索引[,结束的索引号])用于字符串截取(重点)——通过索引号来截取字符,但是注意:包前不包后!实例方法
startsWith(检测字符串[, 检测位置索引号])检测是否以某字符开头(重点)——以字符串为实参
const str = 'pig是只🐖' |

- 实例方法
includes(搜索的字符串[, 检测位置索引号])判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false(重点)——是否含有这个字符,无论位置

- 实例方法
toUpperCase用于将字母转换成大写 - 实例方法
toLowerCase用于将就转换成小写 - 实例方法
indexOf检测是否包含某字符 - 实例方法
endsWith检测是否以某字符结尾 - 实例方法
replace用于替换字符串,支持正则匹配 - 实例方法
match用于查找字符串,支持正则匹配
注:String 也可以当做普通函数使用,这时它的作用是强制转换成字符串数据类型。
案例:清洗数据


等价于:
Number
Number 是内置的构造函数,用于创建数值。
<script> |

总结:
- 推荐使用字面量方式声明数值,而不是
Number构造函数 - 实例方法
toFixed用于设置保留小数位的长度
案例:
// 1. 处理数据 |
小结:
- 思路是先渲染成页面,再替换部分数据(比起先处理数据后渲染,更连贯,不会出现二次)
- 处理数据,把数据转化为数组,便于遍历,拿到每一个值
- 当需要多次使用对象中的属性值时,使用解构
- 转化为数组后,遍历数组,既可以遍历又可以处理成的HTML字符,三步split().map().join(),处理字符串,处理好字符串后,又拼凑成字符串,用于修改内容!
- 注意,当对象中不一定有这个值时,需要先判断,后处理!三元运算符!
- 先变成好集中处理的数据,数组最合适,大部分数据通过遍历,依次处理
- 对象处理数据的优势是,汇聚不同类型的值,数组处理数据的优势是,集中同等类型的值!
- 解决小数精度问题,先把小数转化为整数,计算后再转化为小数!
总结:
- 自制构造函数,使用自定义构造函数创建对象!构造函数的规则,构造函数的主要作用是初始化对象!
- 不要搞混构造函数与对象!如果是想创建带有特殊方法的对象,那么使用构造函数,如果想拥有解决问题的方法,那么使用对象!对象可以自带方法,构造函数不仅可以自带方法,还可以返回对象!
- 构造函数内部,写的属性根本不能使用!构造函数是个函数,虽然特殊,但毕竟是函数,需要调用,才能执行代码,一般构造函数是充满this,使用new执行时,this指向调用者,并添加值!从而生成对象!
- 直接使用构造函数的名字,返回个函数;直接调用构造函数,也没有任何作用;说明构造函数就是专门用于生产对象的!特别的,构造函数也可以像对象一样,通过点来添加属性!
- 本来只有对象,可以在内部添加属性和方法的,并且只有对象才能使用方法,但是在日常使用过程中,不免发现数组、字符串,甚至数字都有方法!这说明JS从底层上,把这些数据类型都处理成了对象!——内置构造函数,JS底层把基本数据类型,通过专门的构造函数变成包装类型!内置构造函数String/Number/Bloon
- 通过内置构造函数,主要讲了数据的处理,利用内置构造函数方法,来快速批量处理数据变成想要的格式!
- 其中处理数组最简洁,把数组处理成字符串,常见处理有遍历=>筛选、求和、映射(修饰)
- 其中数组中的方法,大多是遍历+回调函数,不断的调用函数来处理每一个元素!回调函数的参数要熟记
- 即,要不断地不断地对数组中的每一个元素进行处理,通过在方法中再传入回调函数,通过回调函数来遍历数组同时,处理每个元素,这种方法基本是数组的专属,其它数据类型很少有这类方法!
- 字符串不需要遍历,所以大部分都是查找方法,拼接、分割、截取、查找等方法
JavaScript 进阶 - 第3天笔记

了解构造函数原型对象的语法特征,掌握 JavaScript 中面向对象编程的实现方式,基于面向对象编程思想实现 DOM 操作的封装。
- 了解面向对象编程的一般特征
- 掌握基于构造函数原型对象的逻辑封装
- 掌握基于原型对象实现的继承
- 理解什么原型链及其作用
- 能够处理程序异常提升程序执行的健壮性
编程思想
学习 JavaScript 中基于原型的面向对象编程序的语法实现,理解面向对象编程的特征。——class—TS才用
面向过程
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。——解决问题的步骤(算法)
举个栗子:蛋炒饭
面向对象
面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。—以对象功能来划分问题(拆分问题)

在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
面向对象的特性:
封装性
继承性
多态性
构造函数
对比以下通过面向对象的构造函数实现的封装:
<script> |
封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
同样的将变量和函数组合到了一起并能通过 this 实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的
总结:
- 构造函数体现了面向对象的封装特性
- 构造函数实例创建的对象彼此独立、互不影响
封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
前面我们学过的构造函数方法很好用,但是 存在浪费内存的问题——封装的函数,也被复制了多份!一样就行
原型对象——prototype——构造函数内
构造函数通过原型分配的函数是所有对象所共享的。——可以解决构造函数中,浪费内存的问题!
属于构造函数的属性,可以称为静态属性!每个构造函数都有,返回一个对象——称为(原型)原型对象!
- JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
- 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
- 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
- 构造函数和原型对象中的this 都指向 实例化的对象
声明构造函数:
function Person() { |
调用构造:了解了 JavaScript 中构造函数与原型对象的关系后,再来看原型对象具体的作用,如下代码所示:
<script> |
通过以上两个简单示例不难发现 JavaScript 中对象的工作机制:当访问对象的属性或方法时,先在当前实例对象是查找,然后再去原型对象查找,并且原型对象被所有实例共享。
总结:结合构造函数原型的特征,实际开发重往往会将封装的功能函数添加到原型对象中。
构造函数中this的指向——(重要)
构造函数和原型对象中的this都是指向 实例化的对象!首先声明在prototype 中的函数想要执行,得先调用,通过实例对象调用时,this指向调用者,也就是实例对象!

案例:给数组添加方法、求最大值、求和
分析:给数组添加得方法,得放在内置构造函数Array的 prototype 里面!
// 传入数组的值呢? 通过this指向调用者本身,再用展开运算符+数学方法 |
constructor 属性——在prototype对象内
在哪里? 每个原型对象 prototype 里面都有个constructor 属性(constructor 构造函数)
作用:该属性指向该原型对象的构造函数, 简单理解,就是指向我的爸爸,我是有爸爸的孩子——构造函数

console.log(Star.prototype.constructor === Star) // 缔造者! |
使用场景:
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值.
但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了
此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
Star.prototype = { |
对象原型——__proto__
为什么实例对象能访问构造函数的prototype ,因为实例对象有__proto__

对象都会有一个属性__proto__,指向构造函数的 prototype 原型对象,于是可以使用构造函数 prototype
原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。
注意:
__proto__是JS非标准属性,取决于浏览器对这个意义的写法,Google写成[[prototype]]- [[prototype]]和
__proto__意义相同,打印出[[prototype]],写代码用__proto__(只读,不可更改)! - 用来表明当前实例对象指向哪个原型对象prototype
__proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
实例对象的对象原型指向该构造函数的原型对象,原型对象和对象原型的constructor 属性又指向构造函数
构造函数和原型中的this,又指向实例对象!

原型继承
继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承的特性。
龙生龙、凤生凤、老鼠的儿子会打洞描述的正是继承的含义。
<body> |
注意:在外面直接赋值,构造函数.prototype = {对象},紧接着就是构造函数.prototype.constructor = 构造函数
注意:对象赋值给多个构造函数时,由于对象是堆,所以赋值的是地址,地址里存放的是同一个对象值!通过容器名(地址)访问进行修改时,多个构造函数的值一同变化!要想不一样,再弄一个构造函数,new 构造函数就能生成不同的对象!
Woman.prototype = new Preson() |
原型链
只要是对象就有对象原型__proto_,对象原型__proto_,指向构造函数的原型!——重复这两个操作!
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链

注意:构造函数的原型对象的对象原型,指向对象的原型对象!
<body> |
原型链——查找规则(为对象查找属性和方法提供一条路)
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)
③ 如果还没有就查找原型对象的原型(Object的原型对象)
④ 依此类推一直找到 Object 为止(null)
⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
注意:一切数据皆对象,对象都有对象原型,对象原型指向构造函数的原型对象
[] => Array.prototype,Array.prototype.__proto__ => Objct.prototype,Object.prototype => null'' => String.prototype,String.prototype.__proto__ => Object.prototype- 对象实例的对象原型 => 构造函数的原型, 构造函数的原型的对象原型 => 对象的原型, 对象的原型指向空
意义:高级的构造函数的原型中存放的属性和方法,可以给子孙使用!只要一个原型中有方法,下面都能使用
console.log([1,2,3] instanceof Array) // true |
案例:面向对象编程模态框

- 把这个模态框看成是一件独立的对象!由构造函数生成的实例对象,不同实例可以修改内部文字!
- 可以把构造函数中的this看成是一个实例对象,给这个对象添加属性和方法!
- 方法一般不写在构造函数体内,写到构造函数的原型对象中!
- 对象内拥有属性和方法,也就是用变量存储这个框的元素对象,所有的数据!DOM节点!
document.createElement('div') |
- 然后用方法,把刚生成的对象添加到页面中,这里把它添加到body中,也就是在原型中添加open方法!
Modal.prototype.open = function () { |
- 然后再添加关闭方法,注意的是这个关闭方法,是与生成的模态框里的元素相关!在原型中添加方法的位置得注意,必须是模态框出现在页面中,才能绑定,于是放在open方法里面!可以把close方法独立再调用,我这里是直接把绑定点击和移除功能,写在了一起!
Modal.prototype.open = function () { |
老师写法:

完整代码:
// 设置生成对象的构造函数,也可以看成是一个即将生成的对象! |
老师的写法更优秀:
- 拆分各项功能,再考虑逻辑层面的,当什么时候,再做某事!
- 当点击移除时,更新当前模态框(我的是阻止更新,必须先关闭才能更新)
const mbox = document.querySelector('.modal') |
总结:
- 原型给构造函数赋予了更多可能性,减少内存压力,子代能访问父代的方法!
- 把页面所有内容,看成一个个对象,这是DOM给出的各种属性和方法!
- 构造函数可以看成生产对象,或者就当个对象看!里面拥有属性和方法,属性是存储的数据,各种信息,构成了这个对象的血肉之躯,而方法是给对象赋予灵魂,让这个对象具有各种功能,最后再理清逻辑层面的关系,不同对象间的关系,功能间的依赖关系!
- 先生产一个个独立的对象,再把各个对象联系起来!通常是绑定事件,让其它对象具有控制该对象的方法!
JavaScript 进阶 - 第4天

ajax和promise学了再学vue或者node,有空学学jquery
深浅拷贝
浅拷贝
首先浅拷贝和深拷贝只针对引用类型
浅拷贝:只能拷贝外一层值,内一层如果存在复杂数据,拷贝的是地址!拷贝浅浅的一层!
常见方法:
- 拷贝对象:Object.assgin() / 展开运算符 {…obj} 拷贝对象
- 拷贝数组:Array.prototype.concat() 或者 […arr]
注意:如果是简单数据类型拷贝值,引用数据类型拷贝的是地址 (简单理解: 如果是单层对象,没问题,如果有多层就有问题)
- 直接赋值和浅拷贝有什么区别?
直接赋值的方法,只要是对象,都会相互影响,因为是直接拷贝对 象栈里面的地址 浅拷贝如果是一层对象,不相互影响,如果出现多层对象拷贝还会 相互影响
- 浅拷贝怎么理解?
拷贝对象之后,里面的属性值是简单数据类型直接拷贝值 如果属性值是引用数据类型则拷贝的是地址
深拷贝
首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
常见方法:
- 通过递归实现深拷贝(自己利用递归函数书写深拷贝)
- lodash/cloneDeep(利用JS库ladash里面的
_.cloneDeep()) - 通过JSON.stringify()实现 (利用JSON字符串转换)
递归实现深拷贝
函数递归:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
- 简单理解:函数内部自己调用自己, 这个函数就是递归函数
- 递归函数的作用和循环效果类似
- 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return
<body> |
注意:判断为其它类型时,会递归函数,这个递归函数中,复杂类型剥层皮变成了简单类型,进行值拷贝!
先数组,进行递归,后对象进行递归!因为在instanceof方法中,数组也属于对象!
js库lodash里面cloneDeep内部实现了深拷贝
<body> |
JSON序列化——太简洁明了了!(换一个维度)
<body> |
异常处理
了解 JavaScript 中程序异常处理的方法,提升代码运行的健壮性。
throw(抛异常)
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
总结:
- throw 抛出异常信息,程序也会终止执行
- throw 后面跟的是错误提示信息
- Error 对象配合 throw 使用,能够设置更详细的错误信息
<script> |
总结:
throw抛出异常信息,程序也会终止执行throw后面跟的是错误提示信息Error对象配合throw使用,能够设置更详细的错误信息
try … catch(捕获异常)
我们可以通过try / catch 捕获错误信息(浏览器提供的错误信息) try 试试 | catch 拦住 | finally 最后 |
<script> |
总结:
try...catch用于捕获错误信息- 将预估可能发生错误的代码写在
try代码段中 - 如果
try代码段中出现错误后,会执行catch代码段,并截获到错误信息 - finally 不管是否有错误,都会执行
debugger
相当于断点调试,长代码里面,帮助打断点!
处理this
了解函数中 this 在不同场景下的默认值,知道动态指定函数 this 值的方法。
this 是 JavaScript 最具“魅惑”的知识点,不同的应用场合 this 的取值可能会有意想不到的结果,在此我们对以往学习过的关于【 this 默认的取值】情况进行归纳和总结。
环境对象this:指的是函数内部特殊的变量 this ,它代表着当前函数运行时所处的环境
普通函数
普通函数的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】,如下代码所示:
<script> |
注: 普通函数没有明确调用者时 this 值为 window,严格模式下没有调用者时 this 的值为 undefined。
开启严格模式也很简单:在需要设置的作用域前,写上'use strict'
箭头函数
箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this !箭头函数中访问的 this 不过是箭头函数所在作用域的 this 变量。
- 箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的
- 箭头函数中的this引用的就是最近作用域中的this
- 向外层作用域中,一层一层查找this,直到有this的定义
<script> |
注意情况1:
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window,因此DOM事件回调函数不推荐使用箭头函数,如下代码所示:
<script> |
注意情况2:
同样由于箭头函数 this 的原因,基于原型的面向对象也不推荐采用箭头函数,如下代码所示:
<script> |
总结:
- 函数内不存在this,沿用上一级的
- 不适用 构造函数,原型函数,dom事件函数等等
- 适用 需要使用上层this的地方
- 使用正确的话,它会在很多地方带来方便,后面我们会大量使用慢慢体会
改变this指向
以上归纳了普通函数和箭头函数中关于 this 默认值的情形,不仅如此 JavaScript 中还允许指定函数中 this 的指向,有 3 个方法可以动态指定普通函数中 this 的指向:
call()——了解
使用 call 方法调用函数,同时指定函数中 this 的值,使用方法如下代码所示:
<script> |
总结:
call方法能够在调用函数的同时指定this的值- 使用
call方法调用函数时,第1个参数为this指定的值 call方法的其余参数会依次自动传给函数做为函数的参数
apply()
使用 call 方法调用函数,同时指定函数中 this 的值,使用方法如下代码所示:
<script> |
总结:
apply方法能够在调用函数的同时指定this的值- 使用
apply方法调用函数时,第1个参数为this指定的值 apply方法第2个参数为数组,数组的单元值依次自动传入函数做为函数的参数,以数组方式传递实参- 返回值 本身就是函数的返回值
可以用来求最大值Math.max.apply(Math, arr)
bind()
bind 方法并不会调用函数,而是创建一个指定了 this 值的新函数,使用方法如下代码所示:
<script> |
注:bind 方法创建新的函数,与原函数的唯一的变化是改变了 this 的值。用于不想执行,只想改变时!
返回由指定的this值和初始化参数改造的 原函数拷贝(新函数),但这个函数是更改过的!
例如:在定时器(延时函数)
// 需求,点击按钮,1秒后开启 |
- call 调用函数并且可以传递参数
- apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
- bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.
防抖节流
防抖(debounce)
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间,例如回城B!可以用于输入框搜索,最后再请求,手机号或者邮箱验证!

实现方式:
- lodash提供的防抖
_.debounce(函数,时间) - 手写一个防抖函数
思路:利用延时函数,如果有则清除延时函数,如果没有,则增添延时函数!延时函数内再调用要执行的函数!
案例:设置一个鼠标滑动时不添加,鼠标静止200ms后添加数字
分析:要执行增数函数,必须只有当前这一个延时函数,也就意味着鼠标滑动事件结束了200ms
let i = 0 |
放在变量污染和扩展通用性,这里可以使用闭包和‘先执行再回调’,并把函数当作参数!
const box = document.querySelector('.box') |
节流(throttle)
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数
对于高频触发的事件:鼠标移动,滚动条滚动、页面缩放

实现方式:
- lodash提供的防抖
_.throttle(函数,时间) - 手写一个节流函数
思路:利用定时器,每次执行前都先判断是否存在定时器,如果存在则不开启新定时器了,记得在定时器内部清空这个定时器
注意:开启的定时器,从内部是不能用clearTimeout(timer)进行清空的!但可以用赋值null
// 增加数字 |

节流综合案例
页面打开,可以记录上一次的视频播放位置
分析: 两个事件:
①:ontimeupdate 事件在视频/音频(audio/video)当前的播放位置发送改变时触发
②:onloadeddata 事件在当前帧的数据加载完成且还没有足够的数据播放视频/音频(audio/video)的 下一帧时触发——简而言之就是页面打开时触发!
谁需要节流? ontimeupdate事件, 触发频次太高了,我们可以设定 1秒钟触发一次!
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script> |
小结:这些知识可以面试前再复习一遍,使用场景不多,先抓住DOM操作!
- 深拷贝,使用JSON字符串转化,轻轻松松
- 处理bug,调试后,再try,然后catch,catch中再抛出错误,最后再finadly
- 环境对象this,是给函数找妈,记住常用的就行
- 改变this指向,call()与apply()是一对,bind()返回的是函数,常用在改变定时器!
- 防抖是一种算法:记住步骤,多写几遍,先可以不写成闭包形式,再整理成闭包形式!
- 节流挺常见:比防抖简单,注意关闭定时器用赋值空!
- 对于on事件,使用自制函数,记得调用!使用lodash库,记得先引入库文件!




