根据2024尚硅谷的最新Web前端学习路线和2024年黑马程序员前端学习路线进行的学习笔记整理之javascript篇
(持续更新中)
本人先是看的尚硅谷的前端js课程,把基础看完之后发现尚硅谷的js基础比较侧重于不同浏览器版本之间的兼容,用的很多方法都是比较老的,后面就转跟黑马程序员的前端系列课程。
后黑马程序员课程相关的源码在Gitee博主@ClearStari99
这里可以查看:https://gitee.com/ClearStari99/front-end-BV1Y84y1L7Nn
其他前端相关学习笔记可以在Gitee博主@努力学习的汪
这里查看: https://gitee.com/hongjilin/hongs-study-notes
文章目录
前端开发环境搭建
为了方便在本地搭建前端项目,笔者通过VSCode进行前端代 码的编写,参考教程如下:VScode前端环境搭建:https://blog.csdn.net/m0_75262940/article/details/141105423
浏览器中的 JavaScript 的组成部分
js常见指令
数学内置函数
名称 | 描述 |
---|---|
Math.ceil() | 向上取整 |
Math.floor() | 向下取整,与parseInt()相似,但后者是可以进行字符串转整数的。 |
Math.round() | 四舍五入 |
Math.random() | 生成 [0,1) 之间的随机数 |
Math.pow() | 幂运算 |
数组arr元素的增删及其他基本操作
名称 | 描述 |
---|---|
arr.push(新增数据) | 将一个或多个元素添加到数组的末尾 ,并返回该数组的新长度(⭐⭐⭐) |
arr.unshift(新增数据) | 将一个或多个元素添加到数组的开头 ,并返回该数组的新长度 |
arr.pop() | 将数组的最后一个元素 弹出(也就是删除),并返回该元素的值 |
arr.shift() | 将数组的第一个元素 删除,并返回该元素的值 |
arr.splice(操作的下标(起始位置) , 删除的个数) | 适合用于删除数组中间的元素 |
js数据类型
undefined, null, boolean, string, symbol, number and object
- 基本(值)类型:
- string:任意字符串
- number:任意数字
- boolean: true/false
- undefined: underfined
- null: null
- 对象(引用)类型:
- Object:任意对象
- Function:一种特别的对象(可以执行)
- Array:一种特别的对象(数值下标,内部数据是有序的)
需要注意的是:
- js中的变量都是保存在
栈内存
中的,基本数据类型的数值
直接在栈内存中存储,值之间是独立存在的; - 对象是保存到
堆内存
中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间,而变量保存的是对象的内存地址
(对象的引用),如果两个变量保存的是同一个对象的引用(即同一个地址)时当一个通过一个变量修改属性时,另一个也会受到影响。 - 什么是对象字面量?
类型判断方法
- 基本类型:
- typeof:直接判断并的得出类型
- 能判断:undefined / number / string / boolean / function
- 不能判断:null / object (都是同一类型) object / array
- instanceof:判断对象的具体类型
- a instanceof object:a(实例对象)是不是object这个构造函数的实例
- a instanceof array:a(实例对象)是不是array这个数组对象的实例
- ===:强制等于,可以判断undefined和null(只有一个值)
- typeof:直接判断并的得出类型
- 对象类型:用instanceof来判断
js声明变量的3种方式
var, let, const
- var:可以在整个程序中使用;若在函数中变量没有用var来声明的话,该变量将自动变为全局变量=>因此,一般函数内都要先声明变量,使其为具有局部作用域的变量。(因为比较老现在开发中尽量避免用var)
- let:只会在声明的范围内使用,并不会将当前值绑定到全局对象中(如:let a = 100; console.log(window.a)就不一定是100)
- const:是个永远不会改变的变量
var myNumber = 5
let ourNumber = "123"
const pi = 3.14
(1)以后声明变量我们优先使用哪个?
- const
- 有了变量先给const,如果发现它后面是要被修改的,再改用let
(2)为什么const声明的对象可以修改里面的属性?
- 因为对象是引用类型,里面存储的是地址,只要地址不变,就不会报错
建议数组和对象使用const来声明
(3)什么时候使用let声明变量?
- 基本数据类型的值或引用数据类型的地址发生变化时
- 比如 一个变量进行加减运算,eg. for循环中的i++
适用赋值运算符存储数值:
声明变量和分配变量是有区别的
js对变量大小写是敏感的
js中加分号";"可以更好区分是否换行,但不加也没关系
转义字符 \
CODE | OUTPUT |
---|---|
’ | 单引号 |
‘’ | 双引号 |
\ | 反斜杠 |
\n | 换行符 |
\r | 回车 |
\t | 制表符 |
\b | 退格 |
\f | 换页 |
函数
函数的属性和方法
函数是一段可重复使用的代码块,用于执行特定的任务。它可以接受输入参数(也可以没有参数),并返回一个值(也可以不返回值)。
函数就像是一个工具,通过调用它来执行特定的逻辑操作,这有助于提高代码的复用性和可维护性。
函数有命名函数
(Named Functions)、匿名函数
(Anonymous Functions)、箭头函数
(Arrow Functions)、立即执行函数表达式
(IIFE - Immediately - Invoked Function Expressions)、构造函数
(Constructor Functions)和事件驱动中的回调函数
(Callback Function)等。
其中匿名函数通常作为其他函数的参数(如回调函数)或者立即执行函数表达式(IIFE)。例如,作为回调函数用于数组的forEach方法
立即执行函数(只会执行一次)
面试常考题:用工厂方法创建对象(批量创建对象)
局限性:使用工厂方法创建的对象,使用的构造函数都是Object,所以创建的对象都是Object这个类型,会导致我们无法区分出多种不同类型的对象。
构造函数(封装 多态)
比如:var per = new Person();
此时,this指的就是新建的对象per,最终会作为返回值赋值给per
改进之后,可调用创建更多不同属性的对象:
跟工厂方法类似,但使用同一个构造函数创建的对象,成为一类对象,比如Person类和Dog类
其中,对象 instanceof 构造函数可以用于检查一个对象是否是一个类的实例(eg. per instanceof Person也就是判断人属不属于人的类型)。
使用同一个构造函数创建的对象,我们称为一类对象
,也将一个构造函数称为一个类
。我们将通过一个构造函数创建的对象,称为是该类的实例
。
箭头函数
ES6中提供的一种更简洁的函数定义方式,并且它的this
绑定与普通函数不同,是由其定义时所在的上下文决定的,而不是像普通函数那样在调用时确定。
这使得它在处理事件处理函数和回调函数等场景中,能够避免this指向混乱的问题。
例如传统的函数定义:
function add(x, y) {
return x + y;
}
用箭头函数可以写成:
function add = (x, y) => x + y;
回调函数
面试常考题:call()、apply() 和 bind()
函数对象的方法call()、apply() 和 bind()是每个 JavaScript 函数都具有的 3 个基本方法
(1) call() 函数:能改变函数内部this指向问题的函数
- 基本用法
var person = { fullName: function(){ return this.firstName + " " + this.lastName; } } var person1 = { firstName:"Bill", lastName:"Gates", } var person2 = { firstName:"Steve", lastName:"Jobs", } person.fullName.call(person1); // 将返回"Bill Gates" ,直接传递函数的实参来改变this指针原本指向的值
- 实现原理:
面试手撕代码题
实现call函数需要将其绑定到function的原型上的,结合上面代码实现call函数功能,如mycall函数所示。Function.prototype.mycall = function(person_n){ person_n.fullName = this; person_n.fullName(); } console.log(person.fullName.call(person2)) // 将输出"Steve Jobs"
上面只是一个简单的实现方法,但不能够返回null的数据,想进一步了解可以参照这个大佬的视频:面试题 - 手撕JavaScript call apply bind 函数
(2)apply()函数:
- call和apply这两个方法都是函数对象的方法需要通过函数对象来调用
- 通过两个方法可以直接调用函数,并且可以通过第一个实参来指定函数中this
- 不同的是call是直接传递函数的实参而apply需要将实参封装到一个数组中传递
(3)bind()
- bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)
- 改变this指向后不会立即执行,而是返回一个永久改变this指向的函数
具体解释和实现bind函数参照:面试官:bind、call、apply 区别?如何实现一个bind?
面试常考题:this和arguments是什么?有什么区别?
调用函数时隐含的属性有两个:this 和arguments
-
this(调用函数的那个环境对象)
this是函数的上下文对象,根据函数的调用方式不同会执向不同的对象(文章后面Dom环境对象中也有提到)。- 以函数的形式调用时,this是window
- 以方法的形式调用时,this是调用方法的对象
- 以构造函数的形式调用时,this是新建的那个对象
- 使用call和apply调用时,this是指定的那个对象
- 在全局作用域中this代表window
-
arguments(封装实参的对象)
- arguments和this类似,都是函数中的隐含的参数
- arguments是一个类数组元素,它用来封装函数执行过程中的实参
- 所以即使不定义形参,也可以通过arguments来使用实参
- arguments中有一个属性callee表示当前执行的函数对象
Web APIs
Web APIs包括:
-
DOM(Document Object Model)文档对象模型
-
BOM(Browser Object Model)浏览器对象模型
Dom
Dom是什么?有什么作用?
- Dom是文档对象模型
- 可以操作网页内容,可以开发网页内容特效和实现用户交互
获取Dom元素的方法
window 对象的属性是代表的全局顶级对象,而D0M Manipulation按操作分为以下几种方式(使用不同的方法我们在dom树中选择一个元素):
- GetElementById()
- GetElementByClassName()
- getElementsByTagName()
按类名标签名分为:因为强大的CSS选择器,此两个更容易用到
- querySelector(‘css选择器’) :参数包含一个或多个有效的css选择器
字符串
,比如document.querySelector('div')
可以用来抓取div标签或者获取该类名称。会选择第一个与选择器匹配的项,并返回该HTMLElement对象。如果没有匹配到,则返回null。 - querySelectorAll(‘css选择器’):参数包含一个或多个有效的css选择器
字符串
,比如document.querySelectorAll('ul li')
。会选择所有匹配项,并返回一个NodeList对象集合。
伪数组
通过以上querySelectorAll方法获取到的就是伪数组,只有一个元素也是伪数组。
形如下:
操作元素内容
- 对象.innerText 属性:
- 将文本内容添加/更新到任意标签位置
- 显示纯文本,
不解析
html标签
- 对象.innerHTML 属性:
- 将文本内容添加/更新到任意标签位置
- 显示纯文本,
解析
html标签
操作元素样式属性
-
通过style属性操作CSS:
对象.style.样式属性 = '值'
赋值时,如果需要,不要忘记加css单位(如 ‘500px’)
需要注意:此方法生成的是行内样式表,优先级比较高。 -
通过类名(className)操作CSS:
元素.className= 'active'
当修改的样式较多时可以用这个方法。因为class是关键字,所以这里用className代替。
此方法会覆盖前面的类名。如果想要两种样式都有,可以把两个类名都加上,像这样:<div class = “nav”> <script> const div = document.querySelector('div') div.className = 'nav box' </script> </div>
-
通过classList操作类控制CSS:为解决className操作会覆盖前面类名的问题,可以通过classList追加和删除类名,这也是最常用的方法:
元素.classList.add('类名') // 追加一个类
元素.classList.remove('类名') // 删除一个类
元素.classList.toggle('类名') // 切换一个类
,此方法常用<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> div{ width: 200px; height: 200px; background-color: pink; } .box{ width: 300px; height: 300px; background-color: skyblue; margin: 100px auto; padding: 10px; border: 1px solid #000; } .active{ color: red; background-color: pink; } </style> </head> <body> <div class="box">文字</div> <script> // 通过classList修改样式 // 获取元素 const box = document.querySelector('.box') // 追加类 add() 类名不加点,而且是字符串 box.classList.add('active') // // 删除类 remove() box.classList.remove('box') // 切换类 toggle() 有就加上,没有就删除 box.classList.toggle('active') </script> </body> </html>
操作样式属性小结:
操作元素表单属性
-
获取:DOM对象.属性名
-
设置:DOM对象.属性名 = 新值
表单.value = '用户名'
表单.type = 'password'
-
自定义属性:
以"data-"开头的都是自定义属性
自定义属性案例:
- 定时器-间歇函数:
-
定时器有什么用?
可以根据时间自动重复执行某些代码 -
开启定时器:
setInterval(函数名, 间隔时间)
,每间隔一段时间调用这个函数,间隔时间单位是毫秒。同时,其返回的是一个id数字。setInterval(function(){ console.log('一秒钟执行一次') }, 1000)
或者
function fn(){ console.log('一秒钟执行一次') } setInterval(fn, 1000) // setInterval(函数名, 间隔时间),函数名后面不跟括号,让定时器函数自己调用该函数。
-
关闭定时器:
clearInterval(变量名)
,其中变量名为定时器的id号。
-
事件介绍
事件监听(绑定)
- 语法:
元素对象.addEventListener('事件类型',要执行的函数)
- 事件监听三要素:
- 事件源:那个dom元素被事件触发了,要获取dom元素
- 事件类型:用什么方式出发,比如鼠标单击clik、鼠标经过mouseover、文本框输入内容等
- 事件调用的函数(事件处理程序):要做什么事
比如点击按钮触发弹窗事件:
const btn = document.querySelector('button')
btn.addEventListner('click', function(){
alert('你好呀!')
})
-
事件监听的版本扩展:
事件监听发展史:
- DOM L0
事件源.on事件 = function(){}
<button id="btn">按钮</button> <script> var btn = document.getElementById("btn"); btn.onclick = function(){ }; </script>
- DOM L1
事件源.addEventListener(事件, 事件处理函数)
区别:on方式会被覆盖,addEventListener方式可以绑定多次,拥有更多特性,推荐使用。
- DOM L0
事件类型
此处案例参考黑马程序员js课程>>>Apis-day2-95-99轮播图完整版鼠标定时器切换、小米搜索框焦点事件、发布评论键盘事件等。
事件对象event

- 语法:如何获取
-
在事件绑定的回调函数的第一个参数就是事件对象
-
一般命名为event、ev、e,比如
元素.addEventListener('click', function(e){})
-
常见事件对象属性:
名称 描述 type 获取当前的事件类型 clientX/clientY 获取光标相对于浏览器可见窗口左上角的位置 offsetX/offsetY 获取光标相对于当前DOM元素左上角的位置 key 用户按下的键盘值,现在不提倡用keyCode(keyCode已废弃)
-
此处案例参考黑马程序员js课程>>>Apis-day2-100-101事件对象event、按下回车发布评论。
环境对象

- 什么是this?
- 它代表当前函数运行时所处的环境,this就是这个对象,这个对象受当前环境影响。每个函数中都有this(环境对象),而this指向这个函数的调用者。
谁调用,this就指向谁
- 它代表当前函数运行时所处的环境,this就是这个对象,这个对象受当前环境影响。每个函数中都有this(环境对象),而this指向这个函数的调用者。
- 非严格条件下:
- 普通函数this指向的是window;
- 事件监听函数this指向的是调用者;
- 箭头函数没有this。
- 通过call、apply和bind方法this也可以改变指向。
回调函数
(上面函数部分已有提及)
事件流
-
事件流与两个阶段说明
- 事件流:事件完整执行过程中的流动路径
- 两个阶段:捕获阶段–>冒泡阶段
简单来说:捕获阶段是从父到子,冒泡阶段是从子到父
实际工作都是使用事件冒泡为主
-
事件捕获
-
事件冒泡
-
阻止冒泡
阻止冒泡流动传播一定是
事件对象e.stopPropagation()
- 扩展了解:阻止默认行为(不是阻止冒泡)
- 我们在某些情况下需要阻止默认行为的发生,比如阻止链接的跳转,表单域跳转
- 语法:
e.preventDefault()
- 小结:
- 扩展了解:阻止默认行为(不是阻止冒泡)
-
解绑事件
-
on事件方式
-
addEventListener方式
-
事件委托
- 学习目标:能够说出事件委托的好处
- 定义:事件委托是利用事件流的特征解决一些开发需求的一种知识技巧,一件需要做很多次的事情,可以委托给别人来一次性完成。
- 优点:减少注册次数,提高程序性能。
- 原理:事件委托其实是利用事件冒泡的特点。
- 给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件
其他事件
页面加载事件
- load事件
监听整个页面资源给window
加
- DOMContentLoaded事件
针对页面初始框架的加载,无需等待样式表、图像等完全加载:
无需等待样式表和图像完全加载,更快,给document
加
元素滚动事件


获取滚动位置:
页面尺寸事件
获取元素宽高:

- 元素的尺寸与位置
- 获取宽高:offsetWidth和offsetHeight
- 得到元素宽高 = 内容 + padding + border
- 获取位置:
offsetLeft和offsetTop
注意是只读属性- 得到位置以 带有定位的父级 为准
- 对于html元素默认有定位,但修改样式定位
position: relative
可以给该元素加相对定位,此时该元素就不带定位了。
- 对于html元素默认有定位,但修改样式定位
- 若没有父级,则以 文档左上角 为准
- 得到位置以 带有定位的父级 为准
- element.getBoundingClientRect()
- 相对与网页可视区域而言的,不是整个网页。
小结:
- 相对与网页可视区域而言的,不是整个网页。
日期对象
- 实例化 new
- 获取当前时间:
const date = bew Date()
- 获取指定时间:
const date = bew Date('2024-12-4 08:30:00')
- 获取当前时间:
- 日期对象方法
方法–>是在对象里面,eg. 日期对象.getFullYear()// 获取日期对象(实例化对象) const date = new Date() // 使用里面的方法 console.log(date.getFullYear()) console.log(date.getMonth() + 1) // 获取月份(0~11)需要+1
-
时间戳(毫秒数,唯一)
-
什么是时间戳:从1970年到当前时刻的
毫秒数
-
获取时间戳的方法:
date.getTime()
- 简写
+new Date()
(记这个) - 使用
Date.now()
:只能得到当前时间戳,而前面两种都可以返回指定时间的时间戳 eg. console.log(+new Date(‘2024-12-5 18:30:00’))
-
DOM节点操作

查找DOM节点
- 父节点
子元素名称.parentNode
属性(不加括号) - 子节点
父元素名称.children
属性 - 兄弟节点
- 上一个兄弟:newxtElementSibling属性
- 下一个兄弟:previousElementSibling属性
增加DOM节点
- 创建新节点:
document.createElement('标签名称')
- 追加节点:
- 插入到父元素的最后一个子元素:父元素
.appendChild
(作为子元素要插入的元素) - 插入到父元素重某个子元素前面:父元素
.insertBefore
(要插入的子元素child, 在哪个子元素前面refchild) - 将child元素添加到refChild的前面去
- 如果refChild元素获取不到,会默认以appendChild为准
- 追加的节点可以是新创建的,也可以是页面上已经存在的(eg. 移动)
- 插入到父元素的最后一个子元素:父元素
克隆和删除DOM节点
- 克隆节点:
元素.cloneNode(true)
- 克隆之后还要追加,如:
- 深克隆:
元素.cloneNode(true)
- 浅克隆:
元素.cloneNode(false)
只克隆标签,没啥用
- 深克隆:
- 克隆之后还要追加,如:
- 删除节点:
parent.removeChild(child)
注意:删除和隐藏节点(display:none)是有区别的,隐藏节点是存在的,但是删除,则是从html重删除节点。
补充:显示节点(display: block)
M端事件(了解)
只在移动端有的触摸效果
touch
相关事件touchstart
touchmove
touchend
swiper插件的使用
M端中常用的插件
BOM
window对象
(目标:学习window对象的常见属性,知道各个BOM对象的功能和含义)
BOM(Browser Object Model, 浏览器对象模型)
定时器-延时/延迟函数setTimeout
- 语法:
setTimeout(回调函数, 等待的毫秒数 )
只执行一次 - 清除延时函数:
clearTimeout(timer)
- 两种定时器的区别:
- setTimeout(): 延时函数 执行一次
- setInterval(): 间隔函数 每隔一段时间执行一次,除非手动清除
js执行机制
js本身是单线程的,但浏览器可以多线程。为了解决js单线程这个问题,利用多核CPU的计算能力,HTML5提出了Web Worker标准,允许JavaScript脚本创建多个线程,则js中出现了同步和异步。
同步任务是主线上的(放在执行栈
上),异步任务是耗时的(放在任务队列
中)。代码执行时,先在执行栈上操作,再到任务队列中。
事件循环event loop
(面试高频)
由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环(event loop)
location对象、navigator对象和histroy对象
-
location对象:获取浏览器的信息
- location.herf:指定具体网址,可以实现跳转页面
- location.search:得到问号’?'后面的部分
- location.hash:获取地址中的哈希值,符号’#'后面的部分
此时页面内跳转不需要切换,比如顶部导航栏各个菜单栏的切换
-
navigator对象:实现首页自动跳转到移动端页面(直接找代码复制就行)
-
histroy对象:实现浏览器的后退和前进功能,但在实际开发中很少用到
本地存储localStorage
(重点)
放在浏览器中的小仓库,防止页面刷新不丢失数据。一旦存储就不会消失,除非手动删除。
-
本地存储分类
localStorage
注意:里面所有的key都需要加引号- 存储数据:
localStorage.setItem('key', 'value')
只能存储字符串数据类型(后期需要注意数据类型转换),在浏览器控制台中的application可以查看 - 读取数据:
localStorage.getItem('key')
- 删除数据:
localStorage.removeItem('key')
本地存储一直存在,需要手动删除
- 存储数据:
-
本地存储处理复杂数据类型(存储对象)
- 取值步骤:
- (1) 存:将复杂数据类型转换成JSON字符串
存储
到本地- 语法:
JSON.stringify(复杂数据类型)
- 语法:
- (2)取:将字符串转换为对象来
读取
- 语法:
JSON.parse(JSON数据类型)
- 语法:
JSON对象 属性和值都有引号,而且是双引号:const obj = { uname: 'pink老师', age: 18, gender: '女' } // (1)将对象obj当字符串的形式传入进去 localStorage.setItem('obj', JSON.stringify(obj)) // 得到的是字符串无法直接使用 // (2)将JSON字符串转换为对象 `取出`的时候使用 const str = localStorage.getItem('obj') console.log(JSON.parse(str))
{ "uname": "pink老师", "age": 18, "gender": "女" } // 取值 查看数据类型 console.log(typeof localStorage.getItem('obj')) // 输出为string
- (1) 存:将复杂数据类型转换成JSON字符串
- 取值步骤:
字符串拼接:数组map和join方法
map方法
遍历数组处理数据,并返回新的数组
。
jion方法
把数组中的所有元素转换成一个字符串,并可以用一个字符隔开,比如:mytext.innerHTML = data.join('<br>')
此处将data数组中的所有元素用
换行符隔开了,并且显示在了mytext这个对象的html页面中。
数组中map+join方法渲染页面思路:
正则表达式
js进阶
作用域、闭包、预解析、解构、箭头函数、构造函数、原型、原型链
高阶技巧:深浅拷贝、异常处理、this、性能优化(防抖、节流)
作用域(scope)和作用域链
目标:了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
作用域就是一个范围,作用域规定了变量能够被访问的“范围”
-
局部作用域:
- 函数作用域:在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。函数执行完毕后,内部变量实际会被清空。
- 块作用域:被
{}
包裹的代码称为代码块,代码块内部的变量将有可能
无法被访问。
-
全局作用域:尽可能避免使用
作用域链
作用域链本质上是底层的变量查找机制
:
- 在函数被执行时,会优先查找当前函数作用域中查找变量
- 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
JS垃圾回收机制(Garbage Collection, GC)及算法
JS垃圾回收机制(GC)
JS中内存
的分配和回收都是自动完成
的,内存在不使用的时候会被垃圾回收器
自动回收
内存的生命周期:
- 内存分配
- 内存使用
- 内存回收
一般情况下,全局变量不会回收,局部变量会自动回收。
内存泄漏:程序中分配的内存由于某种原因程序未释放
或无法释放
叫做内存泄漏
垃圾回收机制算法
栈:存放基本数据类型
堆:存放复杂数据类型,垃圾回收机制主要回收复杂数据类型。
常见垃圾回收机制算法:
-
引用计数法:
- 定义:看对象是否有指向它的引用,没有引用了就回收对象
- 缺点:但当嵌套引用也就是循环引用时,两个对象相互引用,尽管不再使用该引用,但垃圾回收器也不会进行回收,容易导致内存泄漏。
因此,此方法现在基本不用了。
-
标记清除法
核心思路是从根部
扫描对象,能查找到的就是使用的,查找不到的就是要回收的。
JS闭包(Closure)(面试高频考点)
-
概念:一个函数对周围状态的引用
捆绑
在一起,内层函数中访问到其外层函数的作用域 -
简单理解:闭包 = 内层函数 + 外层函数的变量
-
闭包的作用:
- 封闭数据,提供操作,外部也可以访问函数内部的变量
- 允许将函数与其所操作的某些数据(环境)关联起来
-
闭包的基本格式:
function outer(){ let a = 1 function fn(){ console.log(a) } return fn } const fun = outer() fun() // 外部fun函数使用了outer函数内部的变量`a`
简约写法为:
function outer(){ let a = 1 return function(){ console.log(a) } } const fun = outer() fun()
-
闭包的应用:实现数据的私有,不容易被篡改
比如,做一个统计函数调用次数的功能
但右边这种闭包形式实现数据私有的方法,会存在内存泄漏(应该被回收但没有被回收)的问题,因为作为全局变量的result始终会用到i,一直不会被释放
- 闭包可能引起的问题:内存泄漏
变量提升(面试考点)
什么叫变量提升:在代码执行之前,会先去检测当前作用域下所有var
声明的变量,并提到当前作用域的最前面,只提升声明,不提升赋值。
因此会造成很多意想不到的bug
,因此ES6
引入了块级作用域,用let
或者const
声明变量,让代码写法更加规范和人性化。
函数进阶
函数提升
函数参数
动态参数arguments
只存在于函数中的一个伪数组
- 当不确定传递多少个实参的时候,我们怎么办?
- 用arguments动态参数
- arguments是什么?
- 只存在函数中的伪数组
剩余参数 ...
- 在实参数量不确定时使用,用于获取
多余
的实参 - 借助 …获取的剩余实参,是个
真数组
注意:写的时候用...other
,使用时只需要写other
展开运算符(...)
与剩余参数非常相似,展开运算符是将一个数组进行展开
写在函数中的是剩余参数,写在外面的是展开运算符
- 剩余参数:函数参数使用,得到真数组
- 展开运算符:数组中使用,数组展开
箭头函数
基本语法
ES6中提供的一种更简洁的函数定义方式,并且它的this
绑定与普通函数不同,是由其定义时所在的上下文决定的,而不是像普通函数那样在调用时确定。
这使得它在处理事件处理函数和回调函数等场景中,能够避免this指向混乱的问题。
例如传统的函数定义:
function add(x, y) {
return x + y;
}
用箭头函数可以写成:
function add = (x, y) => x + y;

箭头函数参数
- 普通函数有arguments动态参数
- 箭头函数没有arguments动态参数,但有剩余参数 …args
箭头函数this
箭头函数没有自己的this,也不会创建自己的this,只会从自己的作用域链的上一层沿用this
DOM事件回调函数不建议
使用箭头函数
遍历数组 forEach 方法(重点)
加强版for循环,只遍历,不返回值
语法:
被遍历的数组.forEach(function(当前数组元素, 当前元素索引号){
// 函数体
})
筛选数组 filter方法(重点)
筛选数组,返回符合条件的数据。
比如,返回数组中大于等于20的数据:
与map相似,但map不能判断,可以运算。
以上代码可以简化如下:
const newArr = arr.filter(item => item >= 20)
console.log(newArr)
构造函数
创建对象的三种方式
- 利用对象字面量创建对象
const o = {
name: '佩奇'
}
- 利用new Object 创建对象
const o = new Object({ name: '佩奇' })
- 利用构造函数创建对象
什么是构造函数
- 是一种特殊的函数,主要用于初始化对象,可以用来
快速创建多个类似的对象
,不需要return但会自动返回创建的新对象。 - 约定:
- 命名以大写字母开头
- 使用构造函数必须加
new
,比如:// 创建构造函数 function Pig(name, age, gender){ this.name = name this.age = age this.gender = gender } // new 关键字调用函数 // new Pig('') 接受创建的对象 const Peppa = new Pig('佩奇', 6, '女') const George = new Pig('乔治', 3, '男') const Mum = new Pig('猪妈妈', 30, '女') const Dad = new Pig('猪爸爸', 35, '男')
- 缺点:在处理复杂数据类型(比如函数)时不同对象会在堆中声明不同的数据,导致浪费内存
=>
可以用原型
的方法来解决这个问题。
实例对象
- 实例对象:构造函数创建的对象成为
实例对象
,都是相互独立的 - 实例成员:
实例对象
中的属性和方法称为实例成员
(实例属性和实例方法) - 静态成员:
构造函数
的属性和方法被称为静态成员
(静态属性和静态方法)- 只能由构造函数来访问
- this指向构造函数
内置构造函数

-
Object
Object.key(对象名称)
Object.value(对象名称)
- 拷贝对象或追加对象属性值:
Object.assign(新的对象名称, 被拷贝的对象名称)
-
Array
- reduce 不用遍历进行累加求和
- 两个参数:
- 第一个回调函数function(){}
- 第二个起始值
- 将伪数组转为数组:
Array.from
-
String
下面这两个方法在实际开发中十分常用:
- 字符串–>数组:
str.split('分隔符')
数组–>字符串:const str = 'pink, red' const arr = str.aplit(', ') console.log(arr)
join
- 字符串截取:
str.substring(indexStart[, indexEnd])
- indexStart:开始截取的索引号
- indexEnd:结束截取的索引号,
不包含结束索引号,可省略
- 检测字符串开头:
str.startWith(检测字符串[, 检测位置索引号])
,根据结果返回True或False - 检测字符串是否包含关键字:
str.includes(searchString[, position])
- 转换为字符串:
num.toString()
- 字符串–>数组:
-
Number
- Number:直接使用传数字
toFixes()
:设置保留小数位数的长度
常见数组操作forEach、filter、map和reduce的区别:
补充:
trim()方法:用于去除字符串两端的空白字符(包括空格、换行符、制表符等),返回一个新的字符串。在判断输入框的字符串是否与对应格式的数据匹配时会用到。
原型和原型链
原型
-
构造函数
-
原型
prototype
是什么?- 一个对象,我们也称
prototype
为原型对象
。
- 一个对象,我们也称
-
原型的作用是什么?
- 共享方法
- 可以把不变的方法,直接定义在
prototype
对象上
-
构造函数和原型里面的
this
指向谁?- 实例化的对象
写在原型上的方法可以共享,这样可以通过共享的方式来减少内存的浪费。
- 实例化的对象
-
公共的属性写在构造函数中
-
公共的方法写在原型对象上
eg. 自己定义 利用数组扩展方法 来求和
和求最大值
// 求和
const arr = [1, 2, 3]
Array.prototype.sum = function(){
return this.reduce((prev, item) => prev + item, 0)
}
console.log(arr.sum())
// 求最大值
const arr = [1, 2, 3]
Array.prototype.max = function(){
return Math.max(...this)
}
console.log(arr.max())
constructor属性
constructor 构造函数
- 概念:每个原型对象中默认都有一个constructor属性,
指向
该原型对象的构造函数
- 作用:在开发中,若有多个对象的方法,可以给原型对象采取对象形式赋值,但这样就会覆盖构造函数原型对象原来的内容(
原型继承
),此时需要重新指回创建这个原型对象的 构造函数
。
function Star(){
}
Star.prototype = {
// 需要重新指回创建这个原型对象的 构造函数
constructor: Star,
sing: function(){
console.log('唱歌')
},
dance: function(){
console.log('跳舞')
}
}
对象原型
prototype
是指原型/原型对象,构造函数制动有原型
原型继承
父构造函数(父类) 子构造函数(子类)
子类.prototype
= new
父类()
子类通过原型来继承父类
原型链(面试高频考点)
__proto__
:属性链状结构instanceof
:用于检测构造函数的prototype
属性是否出现某个实例对象的原型链上
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链
原型链的查找规则:
深浅拷贝
浅拷贝
浅拷贝是创建一个新对象,这个新对象有着原始对象属性值的一份精确拷贝。如果属性是基本数据类型,拷贝的就是基本数据类型的值;如果属性是引用数据类型,拷贝的就是内存地址,所以新对象和原始对象的引用数据类型属性会指向同一块内存地址。
深拷贝
深拷贝是指完全克隆一个对象,包括对象的所有嵌套属性。新对象和原始对象是完全独立的,对新对象的任何修改都不会影响原始对象。
- 用
clodash
的_.cloneDeep(value)
实现深拷贝
- 用
JSON
实现深拷贝
异常处理
throw抛异常
try/catch捕获异常
debugger
处理this
call()(🌟🌟)
可以改变调用函数,可以改变this指向,并且传递参数
apply()(🌟🌟🌟🌟)
可以改变调用函数,可以改变this指向,并且传递参数(参数必须是数组
)
返回值(本身就是在调用函数,所以返回值就是函数的返回值)
使用场景:求Math.max()数组的最大值
bind()(🌟🌟🌟🌟🌟)
bind不会调用函数
能改变this指向
因此,当我们只想改变this
指向,不想调用这个函数时就用bind
使用场景:改变定时器内部的this
指向

性能优化
防抖和底层实现(debounce)
lodash库中的debounce函数
实现防抖的js库
_.debounce(执行的函数, [等待时间], [选项])
debounce函数的底层实现
手写防抖函数

节流-throttle
只执行完整的一次
lodash库中的throttle函数
节流的底层实现

节流小结
防抖节流总结: