JavaScript
JavaScript简介
javascript 是什么
java: 类似java计算机编程语言
script: 脚本
类似java计算机编程语言的脚本语言,缩写为js
java 和 javascript 的区别
-
java 静态编译,js 作为脚本动态编译
-
java 是强类型语言,js 是弱类型语言
javascript 的作用
提供了一个和html进行动态交互的功能
javascript安装与使用
安装
js 运行只需要一个js运行环境即可,那么最常见的js运行环境是浏览器。所以只需要安装浏览器即可。
使用
-
在浏览器控制台上直接运行js语言片段
-
在html文件的script标签中
-
使用.js文件书写js代码,然后再html中通过script标签引入代码
js语法简介
js语法名称为:ECMA Script
Ecma国际(前身为欧洲计算机制造商协会,European Computer Manufacturers Association)
ECMA Script 缩写为 ES
常见说法ES5、ES6,后面的数字,代表的是语法版本号
什么是API
API 是 Application Programming Interface 的缩写,翻译过来就是 “应用程序编程接口”
API 是提供一些用于编程工具,对于 js 来说,API 可以是某框架提供的变量或函数,也可以是网络上的一个可访问的url地址,称为网络接口
API 既然是编程工具,那么其作用是通过调用 API 来实现一个功能,例如:排序,查询,添加数据等操作
由于 JS API 数量众多,课程中将每日介绍一些常用的 API
js运行原理
参考文献:
深入:微任务与 Javascript 运行时环境 - Web API 接口参考 | MDN
https://dmitripavlutin.com/javascript-promises-settimeout/
js 执行上下文(execution context)
执行js的内存区域
3种地方会创建执行上下文
-
全局执行上下文: js程序一开始运行就创建的上下文 (页面 script 标签内的内容就是再全局执行上下文上运行的)
-
函数执行上下文: 函数运行时会创建一个用于运行函数的执行上下文
-
eval(): eval() 函数调用时,其内部字符串被当作脚本执行时,会创建执行上下文
js 运行时(runtime)
用来调度和维护任务,处理事件循环的地方,一个js运行时,包括以下几个部分
-
执行上下文栈
-
执行上下文堆
-
消息队列
1. 任务队列(宏任务队列)
2. 微任务队列
可视化表达为:
事件循环
由js运行时处理的事件或其他任务,都会被加入到队列中,任务不断的被加入队列,从队列取出执行,又加入队列,又被取出执行的过程称为事件循环
类似如下代码:
while(waitForMessage()) {
processMessage()
}
注意: 队列的执行顺序始终是 先进先出
任务队列 (task queue)
也叫 宏任务队列
该队列中的任务将在一个事件循环周期开始时被调用
事件循环周期开始后,再被加入的任务,将不会在这一个周期内被调用
哪些方法可以创建宏任务呢?
例如: setTimeout
或 setInterval
创建的任务将被加入该队列
微任务队列 (microtasks queue)
该队列中的微任务将在一个宏任务执行结束后,依次执行队列中的所有微任务
并且微任务队列没有执行完的情况下,若再加入微任务,该微任务依然会被执行,微任务队列的执行直到队列中所有任务全部完成为止
哪些方法可以创建微任务呢?
例如: Promise
的 resolve
函数调用时,会创建一个微任务
javaScript代码执行顺序
-
有多个script,从上而下依顺序执行
-
同一个script标签中,顺序:从上到下,从左到右,每句话用分号隔开
-
代码中只有一句js,末尾分号可以省略
-
赋值运算顺序,
- 先执行赋值符右侧代码,再赋值给左侧
-
例子
// 以下代码执行顺序: // 从左到右执行,若存在函数,先执行函数 // 若在执行的函数中存在其他函数,例如下面的fn1的调用,那么会先执行内部的函数,如 fn3 fn2,最后执行 fn1 c = fn2() + 4 + fn1(fn3(), fn2(), 11)
引入js代码
-
使用script标签引入外部js文件 src为文件路径
-
script标签写在body后面
变量
什么是变量
// 变量就是存储数据的容器
// 变量是一个可以变化的量,数据可以发生变化
关键字
// js 中预留了一些代表特殊含义的英文单词,这些单词我们称为关键字
变量的命名规范
-
必须以字母开头,或者使用 $ 或者 _ 开头 ,不能用数字,变量名建议不要使用中文
-
小驼峰命名法: 多个单词构成的名字,第一个单词全小写,后面的首字母大写
-
为了提高代码的可读性,命名规范,见名知意
-
不能使用关键字(有特殊功能的名字:class、var、this)
let _a // 下划线开头的变量一般是局部变量出现在函数中作为形式参数
let $body // $ 开头一般是jquery对象
let bird // 用字母开头
let blueBird // 多个单词,首个单词的首字母小写,其余单词的首字母大写
let simpleDateFormat
// let const // 由于const是关键字,所以不能用作变量名
声明变量var
// 声明变量
// 语法:var 变量名
var a; // 声明后不赋初始值 默认都为 undefined
// 声明变量并赋予初始值
var b = 5;
// 同时声明多个变量并赋予初始值
var c, d = 10, e;
// 修改变量
// 变量在存储新值之前讲完全弃用原来的值
c = 2
声明变量let
// 还可以使用 let 来声明变量
// let 声明变量和使用变量的方法和 var 没有区别
let x, y, z = 88
// let 变量不能重复定义 var 变量可以重复定义
常量
// 常量
// 恒定不变的值为常量
// 声明时必须赋初始值 且声明后无法修改
const g = 10
const pi = 3.1415926
// 访问变量或常量
// 变量值存进容器以后,我们需要从容器中取出来进行使用,这个过程就叫访问
json对象
// 声明json对象 (或叫做朴素对象 plain objcet)
let obj = {
// key: value 数据格式的键值对
// key 我们称为 键、对象的属性名
name: '张三',
sex: 'male',
age: 17
}
let obj2 = { a: 1, b: 2, c: 3 }
// 访问对象和对象属性
console.log(obj)
// 使用 . 运算符读取对象中的属性
console.log(obj.age)
// 使用索引来读取对应的数据
// 站在字典的角度来看,name 就是目录,"张三" 就是目录对应的内容
// 目录是唯一的
console.log(obj['name'])
// 给对象属性赋值
obj.name = '法外狂徒'
// 通过索引赋值对象属性
obj['sex'] = 'female'
console.log(obj);
数据类型
数据类型是什么
js中对不同种类数据的一个类型分类
数据类型分类
7种原始类型
-
undefined
-
0Boolean
-
Number
-
String
-
BigInt
-
Symbol
-
null
最后一种类型
Object
数据按照参数的传递方式分为
-
值传递类型
-
引用传递
typeof ----用于判断数据类型
let obj = {}
console.log(typeof obj) // => 'object'
按照值类型
-
Boolean 布尔型
-
Number 数字型
-
String 字符串型
-
BigInt 长整数型
引用类型
-
Object 对象类型
-
Symbol 符号类型
undefined 未定义
是一个单独的类型,用于给未定义的变量赋值
null 空引用
是js的一个原始数据类型,用来指代引用类型数据的空值
值类型和引用类型数据的区别
-
值类型:变量中直接存储值本身
-
引用类型:变量中存储的是引用地址,而值存在引用地址所指向的内存中某个对应位置
数据类型详细
1- Undefined 未定义类型
一个变量声明后不赋值,则他的值为undefined
未定义类型中只有一个值为 undefined
let a
console.log(a); // => undefined
a = 123
// 赋值 undefined
a = undefined
console.log(a);
console.log(typeof a); // => undefined
2- Boolean 布尔类型
用于描述数据对错真假,只有两个值 ture 真;false假
let a = true
// 判断数据类型
console.log(typeof a); // => boolean
3-Number 数字类型
数字类型用于描述数字
-
其中Infinity 无限大的数字
-
// 代表无限大的数字 a = Infinity console.log(typeof a); console.log(123 / 0);
-
-
NaN 代表不是数(not a number)
-
a = NaN console.log(typeof a); console.log('abc' / 123); // 运算结果不是数字 所以显示 NaN // 有时需要判断一个值是否是NaN // 可以使用以下方法 console.log(isNaN(a));
-
-
浮点数 a=0.618
-
科学计数法
-
a=3e+3 a=3e3 //300
-
a=3e-3 //0.03
-
单双精度浮点数是什么意思
-
单精度浮点数是32位的小数
-
双精度浮点数是64位的小数
-
-
4- String 字符串类型
将很多的字符串起来就是字符串
-
可以使用单双引号来定义字符串
// 可以使用单双引号来定义字符串 let str = 'hello world'//单引号定义 console.log(typeof str); str = '' // 空字符串 console.log(typeof str); str = "hello string"//双引号定义 console.log(typeof str);
-
转义
-
转变字符原有的含义
-
转义字符:使用反斜杠\进行转义后的字符
-
常见转义字符:\n 换行 \t制表符(差不多tab宽的空格)
-
单引号字符串显示单引号的方法
str = 'abc\'d'
-
双引号字符串显示双引号的方法
str = "abc\"d"
-
通常可以在双引号中直接写单引号,单引号字符串中直接写双引号
str = 'abc"d' console.log(str); str = "abc'd" console.log(str);
-
两个值通过加法运算符连接,任意一个值为字符串,则进行拼接字符串
let a = 'hello', b = 123 console.log(a + b);
-
-
5- BigInt 长整数类型
用来描述一个位数特别长的整型数字
let a = 1n;
console.log(a);
console.log(typeof a); // => bigint
bigint 类型只能和另一个bigint类型的数进行运算
6- Symbol字符类型
用来创建类中的匿名属性或函数
Symbol.for Symbol.keyFor 可以访问全局Symbol注册表
- 全局注册表可以存放全局可以访问的Symbol值
Symbol作为key声明的对象属性 无法被枚举
symbol作为key声明的对象属性 只能通过相同的symbol值访问
// 定义一个Symbol类型的值
let s = Symbol()
let s2 = Symbol('abc')
console.log(s);
console.log(s2);
class A {
text = 'hello world'
#msg = '被你发现了'
#symbol = Symbol()
constructor() {
this[this.#symbol] = function () {
console.log('这是个匿名函数');
}
}
test() {
console.log(this.#msg);
console.log(this[Symbol.for('add')](1, 2));
this[this.#symbol]()
}
// Symbol.for Symbol.keyFor 是访问的全局Symbol注册表
[Symbol.for('add')](x, y) {
return x + y
}
}
let a = new A()
for (const key in a) {
console.log(key);
}
7- null 空类型
唯一值就是null
null的含义:用来代表对象类型的空引用(空指针)
let a = null
console.log(a); // => null
// 由于 null 代表的是 引用地址(指针) 又因为 js 把引用地址理解为对象,所以此处输出 object
console.log(typeof a); // => object
8-Object类型
对象是一组数据的集合
所以能够被构造的(包含constructor构造器)类型都是对象
所以函数 数组 本质也是对象
朴素对象(plain object)也叫json对象
let obj = {
name: '张三',
sex: 'male',
age: 17
}
-
json:是js轻量化数据结构
-
json文件特点
-
json文件中,根节点只能有一组{}花括号或一组[]方括号
-
属性key必须有一组双引号包裹 value若是字符串,必须是双引号
-
每个属性由逗号隔开,最后一个属性后面的逗号必须省略
-
-
如何声明一个json对象
-
直接用json格式
// 1. 直接使用json格式 let user = { "name": "张三", "sex": "male", // 可以使用字符串去定义key 'age @!*dfksjf': 17 }
-
key不包含空白符和其他特殊符号时,可以不加引号
// 2. key 不包含空白符和其他特殊符号时 可以不加引号 user = { // 字符串可以不用双引号 name: '张三', sex: 'male', age: 17 }
-
变量名若和对象的属性名相同,可以省略
let name = '法外狂徒男枪' let sex = 'other' let age = 17 // 3. 变量名若和对象的属性名相同,则可以省略 如下: user = { // 完整写法 // name: name, name, sex, age }
-
-
和函数都属于对象类型
// 数组和函数都属于对象类型
let arr = [1, 2, 3]
function fn() { }
console.log(typeof arr);
console.log(typeof fn); // => function 虽然 typeof 检测函数结果为 function 但由于函数包含 constructor 所以函数本质上是对象类型
// 学习面响对象的时候再介绍类实例
对象的分类
js中有多种方式可以创建对象,不同的对象从含义,称呼到作用,有略微的区别
除此之外,对象的访问和对象属性的访问方法都是相同的
json 对象
json(JavaScript Object Notation, JS 对象简谱) : 轻量化js数据结构
为什么使用json对象,作用是什么?json 对象主要用于存储数据,有着简洁的数据结构,正由于这样的特点,json 对象常用于网络信息传输中,传递数据
实例对象
实例的概念来自于面向对象编程,计算机看待整个世界时会把所有的东西,抽象成类(class),和类型中的某个个例,这个个例称为实例,例如:人可以抽象成人类,某个具体的人,例如张三,就是一个实例。
通过 class 创建出来的对象,称为实例对象(也叫类实例)
dom 和 bom 对象
dom(document object model) 和 bom (bowser object model) 对象,是浏览器中,使用js时,浏览器提供的两组内置对象,dom 对象对应 html 文档中的标签,bom 对象对应的是浏览器的一些工具
还有一些框架的特殊对象
有些框架会创建出一些特殊的对象,具有特殊功能和名称,例如 jquery 对象,vue 对象等等
数据类型的转换
boolean类型转换成其他类型
-
转换成数字 —ture为1,false为0
let b = true // 转换数字 b = Number(b) // 除了 Number 以外 还能使用 parseInt parseFloat 进行转换 console.log(b); // b = false b = Number(b) console.log(b) // => 0 // true 转换数字为 1 false 转换数字为 0=> 1
-
转换成字符串 — 用+连接字符串时,js隐式转换为字符串
b = true console.log(b); b = String(b) console.log(b); // => true 的字符串 b = false b = b + '' // 连接字符串时,js会自动转换数据格式为字符串,这是一个隐式转换 console.log(b); // => false 的字符串
Number类型转换成其他型
-
转boolean —数字为0结果为false,否则为true
let num = -123 // 转boolean num = Boolean(num) console.log(num); // => true num = 0 num = Boolean(num) console.log(num); // => false // 数字为0 结果为 false 否则结果为 true
-
转字符串
// 转字符串 num = 1314 num = String(1314) console.log(num); // => 1314
String字符串转其他类型
-
转boolean -----字符串为false,其他为true
// 字符串转其他类型 let str = 'hello world' // 转boolean值 str = Boolean(str) console.log(str); // => true str = '' str = Boolean(str) console.log(str); // => true // 空字符串为 false 其余为 true
-
转数字
-
—加法运算符直接写在前面可以产生转换成数字;
-
—若字符串代表的是合法数字转换成数字,否则为NaN
-
// 转数字
str = 'hello world'
str = Number(str)
console.log(str); // => NaN
str = '6e+2'
str = Number(str)
console.log(str); // => 600
// 加法运算符直接写在字符串前,可以产生转换成数字的效果
str = '6e+2'
str = +str
console.log(str);
// 若字符串代表的是合法数字则转换为对应数字 否则为 NaN
Object对象转其他类型
-
转boolean类型
- 给对象赋值为null或者undefined 转换成boolean的值都为false,对象只要存在就为ture
let obj = { a: 1, b: 2 } // 转 boolean 值 obj = Boolean(obj) console.log(obj); // => ture obj = null // 此处赋值 undefined 效果一样 obj = Boolean(obj) console.log(obj); // => false // 变量是 null 或 undefined 时 转换结果为 false;对象只 要存在 结果就为 true
-
转数字 ----为NaN
// 转数字 obj = { a: 1, b: 2 } obj = Number(obj) console.log(obj); // => NaN
-
转字符串
-
对象强转字符串结果始终为 [object Object]
-
若希望将对象转换成字符串后能够查看其数据,则可以使
// 转字符串 obj = { a: 1, b: 2 } obj = String(obj) console.log(obj); // [object Object] // 对象强转字符串结果始终为 [object Object] // 若希望将对象转换成字符串后能够查看其数据,则可以使用 “序列化”
-
序列化与反序列化
-
序列化 -------JSON.stringify()
- 将对象转换成字符串 JSON.stringify()
-
反序列化 -----JSON.parse()
-
将字符串转换成对象
let obj = {
name: '张三的母亲', sex: 'other', age: 40 } // 序列化 let str = JSON.stringify(obj) console.log(str); // 通过json格式声明一个字符串 str = '{"name": "英雄的母亲", "child": "张三", "score": 88, "isOk": false}' // 反序列化 obj = JSON.parse(str) console.log(obj);
-
Reflect反射
反射是用来操作对象的方法
文档: Reflect - JavaScript | MDN
给对象添加属性
-
obj.c=3 ----给obj添加属性值为3的属性c
-
obj[‘e’]=4 —给obj添加属性值为4的属性e
给对象定义属性
Object.defineProperty(obj,‘需要定义的属性名’,{value:3})
// Object.defineProperty(obj, 'c', {
// // 属性值
// value: 3,
// // 是否可写
// writable: false
// })
Reflect.defineProperty(obj,‘c’,{value:5})
-
第一个参数:要定义属性的对象
-
第二个参数:要定义的属性名称
-
第三个参数:配置对象
Reflect.defineProperty(obj, 'c', { value: 5, writable: true })
删除属性
返回对象的所有key数组
Reflect.ownKeys(obj)
- 参数是对象
判断是否存在某属性
-
Reflect.has(obj,‘c’)
-
第一个参数:要判断属性是否存在的对象
-
第二个参数:要判断是否存在的属性名
-
-
使用if的隐式转换
使用if的隐式转换 if(obj.e) { // obj.e 若等于以下值: null undefined '' 0 ==> false 其余情况为 true // if 语句将隐式转换数据为 boolean 值 // 存在e console.log('e存在'); }
-
使用typeof
console.log(typeof obj.e)
浏览器操作
bom和dom
dom
js中的对象,用来操纵浏览器中的元素
bom
js中的对象,用来操纵浏览器,不同的bom对象代表不同的功能
window
-
window对象的相关属性和方法,再调用的时候可以省略window
例如 window.open() 可以写作 open()
打开窗口
window.open(‘网址’,‘iframe名称,没有就打开新窗口’)
// 打开窗口
// 第一个参数:网址
// 第二个参数:iframe名称,没有的话就打开新窗口
// window.open('./child.html', 'child')
关闭窗口
window.close()
窗口间的通信
原理:
-
通过窗口对象(window)发送消息(postmessage)
-
另一个窗口通过message事件来接收消息
-
消息: 发送
location
-
代表浏览器地址栏
跳转网页
-
给location.href赋值就会跳转
-
let input = document.querySelector(‘input’)
let button = document.querySelector('button') button.addEventListener('click', () => { // 给 location.href 赋值页面就会调转 window.location.href = input.value })
跳转网页不计入历史
-
window.location.replace
-
// 跳转网页不计入历史
// window.location.replace('https://www.bilibili.com’)
刷新页面
window.location.reload()
地址栏参数
location.search
history 浏览器浏览记录
前进
window.history.foward()
后退
window.history.back()
转到指定位置
history.go(deta)
-
// history.go(1) 等价于 history.forward()
-
// history.go(-1) 等价于 history.back()
localStorage
数据持久化到浏览器中,关闭窗口或浏览器,都不会消失
-
设置数据
-
window.locaStorage.setItem(‘name’,‘张三’)
-
localStorage[‘sex’]=‘male’
-
-
读取数据
-
localStorage.getItem(‘name’)
-
localStorage[‘sex’]
-
sessionStorage
用法和localStorage一样,但是关闭窗口数据消失
// window.sessionStorage.setItem('age', '17')
navigator 用于查看设备信息
-
文档:Navigator - Web API 接口参考 | MDN
-
console.log(navigator);
节点操作
查询节点
dom对象中可以查询其父节点和子节点
-
查询元素:document.querySelector()
// 获取tr let tr = document.querySelector('.tr')
-
访问子节点使用 children 属性
-
let tr = document.querySelector(‘.tr’)
-
children 属性包含一个子节点的集合,所以访问具体子节点可以使用索引值
-
// children 属性包含一个子节点的集合
// 所以访问具体子节点可以使用索引值 let node = tr.children[1] node.remove()//删除
-
-
访问父节点使用 parentElement 属性
插入节点
-
步骤
-
创建节点
-
document.createElement(‘标签名‘)
-
let img = document.createElement(‘img’)
// 设置图片源 img.src = '../img/head.png'
-
-
插入节点到文档中
-
1appendChild (追加的元素),追加一个子元素
-
// appendChild 追加一个子元素
// 参数是要追加的元素 document.body.appendChild(img)
-
-
+ 2 insertBefore (要插入的元素,插入位置的子节点)
插入节点到另一个节点前
// insertBefore 插入节点到另一个节点前
// 第一个参数:要插入的元素
// 第二个参数:插入位置的子节点
document.body.insertBefore(img, ok)
删除节点 remove()
- 删除元素
// 节点的remove可以删除元素
// li.remove()
-
removeChild 删除子节点
// removeChild 删除子节点 // 参数是想要删除的子节点dom对象 ul.removeChild(li)
-
动态加载一次性js脚本资源
-
下载按钮
// 用途:
// 动态加载一次性js脚本资源,加载完后就可以删除元素了 // 例如:下载按钮 btn.addEventListener('click', () => { // 动态创建a标签并点击它 来实现下载 let a = document.createElement('a') // download 可以指定下载后的文件名 a.download = '1.png' // 下载文件的地址 a.href = '../img/head.png' // 无需插入页面即可点击 a.click() // 若a标签以插入页面则需要调用 remove 来删除 a.remove() })
-
替换节点parent.replaceChild(newNode,child)
-
parent.replaceChild(newNode, child)
parent: 要替换节点的父节点
newNode: 新的要插入文档的节点
child: 被替换的节点
-
// 构造一个新的元素用于替换
let td = document.createElement('td') td.textContent = 'hello world' tr.replaceChild(td, _td)
下载文件
<body>
<!-- 点击下载按钮,动态下载文件 -->
<button>下载</button>
<!-- a标签中 href填入要下载的文件路径即可,可以是网络路径 -->
<!-- <a download="1.png" href="../img/head.png">head.png</a> -->
</body>
<script>
// 下载文件的实现方式,实际上就是使用 <a> 标签
let btn = document.querySelector('button')
btn.addEventListener('click', () => {
console.log('click')
// 创建 a 标签
let a = document.createElement('a')
// 设置下载文件的名称
a.download = '1.png'
// 给 a 标签的 href 属性赋值
a.href = '../img/head.png'
// 使用js代码触发a标签的点击事件
a.click()
})
</script>
异步加载js文件
<body>
<button>异步加载js</button>
</body>
<script>
let btn = document.querySelector('button')
btn.addEventListener('click', () => {
// 创建script
let script = document.createElement('script')
script.src = '../js/main.js'
// load 加载完成事件
script.addEventListener('load', () => {
console.log('加载完成');
// 删除脚本
script.remove()
})
// 插入页面
document.body.appendChild(script)
})
</script>
运算符
运算符
算术运算符
算术运算符:+ - * / % **(幂运算)
加减乘除
-
// 加减乘除符号
let a = 1, b = 2 console.log(a + b); console.log(a - b); console.log(a * b); console.log(a / b);
-
加号特殊情况
-
1连接字符串
console.log(a + 'hello world');
-
-
2转换字符串为数字
console.log(+'3e-2');
%模运算 -----除以一个数取余数
console.log(1 % 2);
console.log(3 % 2);
console.log(5 % 3);
**幂运算 ----运算n的m次方
console.log(2 ** 3);
console.log(4 ** 3);
注意
-
算术运算符遵循数学的符合优先级
-
可以使用圆括号分组
赋值运算符
+= 自增
- a+=2 等价 a=a+2
-=自减
- a -= 5 // 等价于 a = a - 5
*= 自乘
- a *= 5 // 等价于 a = a * 5
/=自除
- a/=2 等价于 a=a/2
%=自模
a%=3 等价于 a=a%3
++自增
–自减
自增自减运算符位置的区别
-
// 运算符放前面,则先运算后引用
-
// 运算符放后面,则先引用后运算
比较运算符
& 符号开头
分号结尾 的内容 我们称为 html 实体
比较运算符: >, <,>=, <=, ==, !=, =, !
比较运算符用于对运算符左右两个值进行比较
比较运算符的运算结果为boolean值,用于指示比较结果为真或者假
大于
console.log(1 > 2);
小于
console.log(1 < 2);
大于等于 小于等于
console.log(3 >= 3)
console.log(3 <= 3)
== 非严格相等判断
-
非严格相等判断
-
在运算符左右两边不需要使用相同的数据类型
-
当数据类型不同时 == 运算符将自动进行隐式转换
-
非严格相等判断 可以不同类型的数据拿来进行比较,所以有时可能会得到期望意外的结果,不建议使用
-
===严格相等 —类型和值都相等才会认为相等
对象的相等判断
-
// 由于对象类型的变量保存的是对象的引用地址
// 所以此处两对象引用地址不同 结果为false
-
let obj1 = { name: '张三' } let obj2 = { name: '张三' } // 由于对象类型的变量保存的是对象的引用地址 // 所以此处两对象引用地址不同 结果为false console.log(obj1 === obj2);
!= 非严格不等于
console.log('' != 0);
!==严格等于
console.log('' !== 0);
逻辑运算符
-
// 逻辑运算符:符合逻辑条件时将返回真或假
-
// bool expression 的意思是:布尔表达式
-
// 什么是布尔表达式?整个js代码运算结果是一个布尔值,这样的代码片段叫做布尔表达式
// 举例如下: console.log(1 === '') // 所有比较判断都是布尔表达式 console.log(true)
&&与
-
语法:bool expression && bool expression
-
两个表达式任一一个为真,结果返回真;否则为假
||或
-
语法:bool expression || bool expression
-
两个表达式任一一个为真,结果返回真;否则为假
&& 和 || 开发人员称为短路运算
-
// 短路运算的应用场景1:赋值默认值
let a = 1 // => undefined let b = a || 5 console.log(b);
-
// 短路运算的应用场景2:按条件执行语句
let x = user.age > 20 x && console.log('user.age 大于 20') user.age > 20 && console.log('user.age 大于 20')
!非 作用是取反
三元(目)运算符
用于判断一个表达式结果,为真时,返回结果1,为假时返回结果2
-
语法:
bool expression? case1: case2
-
作用:
主要用在给变量赋值
let obj = { name: '张三', sex: 'other', age: 16 } console.log(obj.sex === 'male' ? '男' : '女'); // 嵌套三元运算符 在问号或冒号处可以换行 console.log(obj.sex === 'male' ? '男' : obj.sex === 'female' ? '女' : '其他'); // 当嵌套三元运算符在同一行内书写,请给嵌套的三元运算符的表达式用圆括号包起来 console.log(obj.sex === 'male' ? '男': (obj.sex === 'female' ? '女' : '其他'));
运算符的优先级顺序
++ – > 算术运算符 > 比较运算符 > 逻辑运算符 === 三元运算符 > 赋值运算符
// switch ( value ) {
// case constant1:
// // code
// break;
// case constant2:
// break;
// … 可以有任意多个case语句
// default: // default 不是必须要写的
// break;
// }
let r = 6
let myCase = 4
console.log®;
switch (r) {
// r 的值可以和 case 中的表达式进行匹配
// 匹配成功则执行后续代码
case myCase + 2:
console.log('myCase')
break;
case 0:
console.log('随机结果为0');
// break 用于跳出 switch 语句
break;
case 1:
console.log('随机结果为1');
break;
case 2:
console.log('随机结果为2');
break;
case 3:
console.log('随机结果为3');
break;
default:
console.log('这是 default')
break;
}
数组和循环
数组
什么是数组?
存储一组数据的一个容器
声明数组
-
使用脚本形式声明数组
-
声明
-
let arr = []
-
声明并初始化数组
-
其中数组的每一个数据称为 数组成员
arr = [false, 2, ‘hello world’, { name: ‘张三’ }, null, undefined]
-
-
-
对象形式声明数组
-
声明
arr = new Array()
-
声明并初始化
arr = new Array(false, 2, 'hello world', { name: '张三' }, null, undefined)
-
访问数组成员
-
使用索引值来访问数组中的成员
-
索引:数组给每个成员的编号称为索引,索引值从零开始一次递增1
-
访问数组成语时,若索引超出数组范围,获取的结果为undefined
console.log(arr[‘0’]);
console.log(arr['1']); // 数组索引可以不使用引号 console.log(arr[2]);
-
给数组成员赋值
使用数组变量名加上索引值进行赋值,可以个 指定索引位置的成员进行赋值
arr[4] = { name: '李四' }
console.log(arr);
arr[6] = 333
console.log(arr);
arr[9] = 555
console.log(arr);
数组长度length
数组长度指数组成员的个数
若赋值的时候索引若不是正整数,该值将被视为数组对象的属性值,不计入数组成员,所以不会影响数组长度
arr[-1] = true
console.log(arr);
console.log(arr.length);
多维数组
什么是多维数组
多个维度(rank)组成的数组
声明二维数组
二维数组中每个一维的成员都是一个数组
arr = [[1, 2], [3, 4], [5, 6]]
读取数组
可以将二维数组视为一个x轴和y轴构成的二维平面,所以任意值都可以由x,y坐标表示
console.log(arr[0][1]);
console.log(arr[2][0]);
声明多维数组
多维数组是数组内嵌套多层数组的数组
arr = [[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]
访问数组
可以理解成三位空间中访问三位坐标系
console.log(arr[0][1][1]);
如何判断一个变量是否是数组
Array.isArray来检测变量是否为数组
是数组返回true 否则返回false
let a = [1, 2]
let b = 'hello world'
console.log(Array.isArray(a));
console.log(Array.isArray(b));
数组的操作
let arr = [1, 2, true, { name: 'Amy' }, 2]
-
push追加数据到数组末尾
-
参数被添加的新数组成员
arr.push('hello world')
-
-
pop从尾部取出一个成员
-
返回值是取出的成员
let r = arr.pop() console.log(r);
-
-
unshift在头部添加数据
-
参数:被添加的新数组成员
arr.unshift('my best day') console.log(arr);
-
-
shift从头部取出一个成员
-
返回值是取出的成员
r = arr.shift() console.log(r);
-
push和unshift可以批量添加成员
arr.push('a', 'b', 'c')
arr.unshift('x', 'y', 'z')
-
splice 删除指定位置的成员,并使用新成员替换,或插入新成员到指定的成员的前面
-
第一个参数:删除指定成员的起始位置
-
第二个参数,删除成员的个数
-
第三个参数,用于替换被删除成员的新数据,该参数可以省略
-
删除一个成员
r = arr.splice(6, 3)
- splice的返回值,就是被删除的成员数组
-
在指定成员追加数据
-
若第二个参数为0则可以实现在指定位置的前面添加成员的功能
arr.splice(6, 0, { name: 'Bob' }) console.log(arr);
-
-
-
concat连接数组
-
参数:多个被追加进数组的成员,若成员是数组,该数组中每个成员将加入原数组
-
concat返回一个新数组
// r = arr.concat(7, 8, 9) // r = arr.concat([7, 8, 9], [10, 11]) // console.log(r); // console.log(arr);
-
concat的应用场景多用于克隆数组
let arr2 = [].concat(arr)
-
-
join 使数组成员用一个字符连接起来
join 函数接收一个参数,该参数就是连接数组成员时使用的字符
- arr2 = [‘abc’, ‘xyz’, ‘123’]
r = arr2.join(‘-*-’)
console.log®;
- arr2 = [‘abc’, ‘xyz’, ‘123’]
-
includes判断是否包含某成员
r = arr.includes('z') console.log(r);
-
indexOf 查询指定数组成员的索引
-
r = arr.indexOf('b')
-
lastIndexOf 查询最后一个指定数组成员的索引 (因为数组成员可能重复)
-
可以使用indexOf判断是否包含某个数组成员 若不包含 返回-1
-
r = arr.indexOf(‘g’)
console.log(r); // => -1 if (arr.indexOf('g') === -1) { console.log('该数组成员不存在'); }
-
-
slice 数组切片 获取子数组
切片”遵循前截后不截“,即数学中左闭区间右开区间
-
r = arr.slice(5, 8)
-
参数只有一个,代表从该位置开始,一直截取到最后
-
r = arr.slice(5)
console.log®;
-
数组的迭代操作
以下所有的遍历函数的参数都是相同的回调函数
回调函数接收以下参数
-
el 被遍历的当前数组
-
index 被遍历的当前成员的索引
-
arr 被遍历的数组对象
-
let students = [
{ id: 0, name: '张三', sex: 'male', age: 16 }, { id: 2, name: '隔壁老王', sex: 'other', age: 30 }, { id: 1, name: '李四', sex: 'female', age: 20 }, ]
forEach 循环遍历每一个数组成员
// forEach 循环遍历每一个数组成员
// students.forEach((el, index, arr) => {
// console.log(el);
// console.log(index)
// console.log(arr);
// })
every遍历每个数组成员,(与forEach相比)但是中途可以跳出循环
// every 和 forEach 一样遍历每个数组成员,但是中途可以跳出循环
// students.every((el, index, arr) => {
// console.log(el);
// console.log(index)
// console.log(arr);
// if (el.sex === 'female') {
// // every 的回调函数中需要返回一个bool值 false 类似于循环语句中的 break 用于跳出循环
// return false
// }
// // 类似于 continue 继续循环
// return true
// })
map映射数组到新数组中
map的回调函数将返回一个值,代表当前被遍历的数组成员在新数组中的投影
map函数将返回一个新的投影数组
// 例如将students中所有的年龄放入一个新数组
// let r = students.map((el, index, arr) => {
// console.log(el);
// console.log(index)
// console.log(arr);
// // return 的内容将被放到新的数组中
// return el.age
// })
// console.log(r);
filter过滤器
filter返回过滤完后的新数组
// let r = students.filter((el, index, arr) => {
// console.log(el);
// console.log(index)
// console.log(arr);
// // 过滤掉年龄大于25岁的成员
// // if (el.age > 25) {
// // // return false 代表过滤掉该数据
// // return false
// // }
// // return true // return true 代表保留该数据
// return el.age <= 25
// })
// console.log(r);
find 查找符合回调函数条件的数组成员并返回它
返回值为查询结果
// let r = students.find((el, index, arr) => {
// // // 查找女同学
// // if (el.sex === 'female') {
// // return true // return true 代表当前成员就是要查找的元素
// // }
// // return false
// return el.name === '隔壁老王'
// })
// console.log(r);
findIndex 查找对应成员所在索引
// findIndex 查找对应成员所在索引
// 使用方法和 find 相同,返回结果为查找到的成员索引
// let r = students.findIndex((el, index, arr) => {
// return el.name === '隔壁老王'
// })
// console.log(r);
some 方法测试数组中是不是至少有1个元素通过了被提供的函数测试
返回值是一个Boolean类型值
-
给出一判断条件,some函数对每个成员都做出了相同的判断
-
任意成员符合条件返回true时,some函数则返回true,否则为false
// 请判断 arr2 中是否有成员存在于 arr1 中 let arr1 = ['x', 'y', 'z'] let arr2 = ['a', 'b', 'z'] // let r = arr2.some((el, index, arr) => { // // 判断当前数组成员是否再 arr1 中存在 // if (arr1.includes(el)) { // // 返回 true 代表 找到一个满足条件的数组成员 // return true // } // return false // }) // console.log(r);
sort排序
-
若调用sort函数不传参数时,会使用浏览器默认的排序规则(从第一位开始比大小排序)
// 若调用 sort 函数不传参数时,会使用浏览器默认的排序规则
let numList = [90, 12, 110, 9, 40, 214] //=> 结果顺序不是想要的 // let numList = [90, 12, 11, 92, 40, 21]//=>结果可以 numList.sort() console.log(numList);
-
自定义排序(冒泡排序)
-
排序规则:按年龄大小从小到大
-
sort参数是一个排序规则的函数
-
el1和el2代表的是排序时两两相比的两个数组成员
-
原理:
将每个成员都和剩下所有成员进行比较,每一对 el1 和 el2 决定他们的先后顺序
-
sort函数执行完后将返回新数组
students.sort((el1, el2) => { if (el1.age > el2.age) { // el1.age 若大于 el2.age 则 若从小到达排列 el1 应该放到 el2 的右侧 // 右侧联想 右箭头 >;即大于符号,则此处应该返回一个大于零的值 return 1 } else if (el1.age < el2.age) { // el1.age 若小于 el2.age 则 若从小到大排列 el1 应该放到 el2 的左侧 // 左侧联想到 左箭头 < ;即小于符号,所以返回一个小于 0 的值 return -1 } else { // 若 el1 和 el2 不需要交换顺序 则返回 0 return 0 } }) console.log(students);
-
数组去重
-
缓存重复数据
-
缓存
// 缓存 let temp = {} // 结果数组 let result = [] for (let i = 0; i < arr.length; i++) { const item = arr[i]; // 判断 item 充当 key 是否存在于 temp 中 // if (temp[item]) { if (!Reflect.has(temp, item)) { // item 不存在于 temp 的 key 中 result.push(item) // 添加缓存 temp[item] = true } } console.log(result);
-
使用filter去重
// 使用 filter 去重 temp = {} result = arr.filter(el => { // 若缓存中没有el if (!temp[el]) { // 加入缓存 temp[el] = true return true } return false }) console.log(result);
-
-
使用set集
// 2. 使用 set 集
// 构造set对象,set对象会自动去重 let set = new Set(arr) console.log(set); // 将set转换为数组 result = Array.from(set) console.log(result); // 合成一句话 console.log(Array.from(new Set(arr)));
mapRudece 统计模型
mapReduce 运算模型是拿来做大数据统计的
要统计出结果,需要经历 map 和 reduce 两个步骤
-
map 的作用 用于返回统计结果所需的数据结构
-
reduce 的作用就是具体的统计逻辑
-
reduce api: Array.prototype.reduce() - JavaScript | MDN
案例
let arr = [
{
name: '李四666',
score: 45,
sex: 'female'
},
{
name: '张三1',
score: 45,
sex: 'male'
},
{
name: '李四1',
score: 89,
sex: 'female'
},
{
name: '老王1',
score: 48,
sex: 'male'
},
{
name: '张三2',
score: 65,
sex: 'female'
},
{
name: '李四2',
score: 32,
sex: 'female'
},
{
name: '老王2',
score: 17,
sex: 'male'
}
]
-
问题1 统计上述数组不及格的人数
let r = arr.map(el => el.score) r.unshift(0) console.log(r); // reduce 中的参数 // p: 第一个数组成员或上一次运行回调函数的返回值 // n: 下一个被遍历的成员 r = r.reduce((p, n) => { // 使用 p 充当当前不及格的人数 if (n < 60) { p++ } // 返回值的数据结构应该和 map 返回的结构相同 return p }) console.log(r);
-
问题2:统计总分和平均分
// 2. 统计总分和平均分 r = arr.map(el => { return { avg: 0, // 品均分 sum: 0, // 总分 score: el.score, // 每个同学的分数 count: 0 // 同学的数量 } }) // 前置插入一个空对象用来充当上一次的结果 r.unshift({ avg: 0, sum: 0, score: 0, count: 0 }) console.log(r); r = r.reduce((p, n) => { // 总分 = 上一次的总分 + 本次的分数 let sum = p.sum + n.score // 人数 = 上一次的人数 + 1 let count = p.count + 1 // 平均分 let avg = sum / count // 返回值的数据结构应该和 map 返回的结构相同 return { avg, sum, score: 0, count } }) console.log(r);
-
问题3:按男女统计分数最低的两个人
// 3. 按男女统计分数最低的两个人 r = arr.map(el => { return { // 男生分数最低的人 male: [], // 女生分数最低的人 female: [], stu: el } }) r.unshift({ male: [], female: [], stu: null }) console.log(r); r = r.reduce((p, n) => { // 获取对应 n 性别的数组 let temp = p[n.stu.sex] // 数组长度小于 2 就无条件添加 if (temp.length < 2) { temp.push(n.stu) } else { // let male = [{ score: 21 }, { score: 34 }] // n.stu.score = 25 // 将新数据加入数组 temp.push(n.stu) // 从小到大排序 temp.sort((el1, el2) => el1.score - el2.score) // 把最后一个删除 temp.pop() } return { male: n.stu.sex === 'male' ? temp : p.male, female: n.stu.sex === 'female' ? temp : p.female, stu: null } }) console.log(r);
流程控制–循环
什么是循环
循环就是重复执行某一段代码的循环
for循环
语法:
for( 初始化语句; 循环条件; code block 运行一次结束后运行的代码 ) { code block }初始化语句:初始化变量值
循环条件:循环条件是个布尔表达式,若结果为真,则执行 code block 的代码块
let index = 0
// for (index; index < count; index++) {
// console.log('hello world');
// }
-
循环的继续与跳出
-
continue; 继续循环,结束本次循环,继续下一次循环
-
break;跳出循环
// for (let i = 0; i <= 100; i++) {
// if (i % 2 === 0) { // console.log(i); // // 继续循环 // // 结束本次循环,继续下一次循环 // continue; // } // if (i > 51) { // console.log('break'); // console.log(i); // // 跳出循环 // break; // } // console.log('i: ' + i); // }
-
-
使用for循环遍历数组
let arr = [ { name: '张三' }, true, 55, 'hello world', [1, 2, 3] ] // for (let i = 0; i < arr.length; i++) { // // 使用 i 充当数组索引来遍历数组 // console.log(arr[i]); // }
for in 循环
语法:
for ( key in arr|obj ) {
code block
}
-
遍历数组
// index: 每个被遍历的数组成员的索引值 // for (let index in arr) { // console.log(index); // console.log(arr[index]); // }
-
遍历对象
let obj = { name: '小红', sex: '妹纸', age: 18 } // key: 对象属性名称 // for (let key in obj) { // console.log(key); // console.log(obj[key]); // }
for of:用于遍历数组,且不能获取索引值,只能遍历所有成员
element:数组中每个成员
for (let element of arr) {
console.log(element);
}
- 可以使用在所有的存在迭代器的集合对象上,如:Set和Map
while 循环:当·····时,就循环执行代码
语法
condition: while循环的条件,是一个布尔表达式,若成立则执行循环的 code block
while (condition) {
code block
}
let a = 0
// while (a < 5) {
// console.log('hello world');
// // while 循环 通常在代码块的末尾 应该修改循环条件
// a++
// }
do while 循环:先执行代码,再判断循环条件
语法
语法:
do {
code block
} while (condition)
a = 0
do {
console.log('hello world');
a++
} while (a < 5)
所有的循环语句,都可以使用break和continue
冒泡排序
原理
-
以排序出一个从小到大的序列为例
令数组长度为 lenght
排序过程将进行 length - 1 次
每一次排序过程中,将对数组中每个成员从左到右进行两两比较,较大的数字将被挤到后面去
每执行完一次循环,将在数组末尾确定一个数字的位置
最后直到数组的第一个成员和第二个成员进行排序为止,排序结束
let arr = [5, 2, 1, 3, 7, 0, 9, 4]
for (let i = arr.length - 1; i > 0; i--) { for (let j = 0; j < i; j++) { // 判断前一个数和后一个数的大小 if (arr[j] > arr[j + 1]) { // 交换 let temp = arr[j] arr[j] = arr[j + 1] arr[j + 1] = temp } } } console.log(arr);
基于冒泡排序的自定义排序规则
// 例如:下列数组按分数排序
let students = [
{
name: '张三',
score: 60
},
{
name: '李四',
score: 40
},
{
name: '老王',
score: 80
}
]
for (let i = students.length - 1; i > 0; i--) {
for (let j = 0; j < i; j++) {
if (students[j].score > students[j + 1].score) {
let temp = students[j]
students[j] = students[j + 1]
students[j + 1] = temp
}
}
}
console.log(students);
函数
* 函数的使用
什么是函数
站在开发人员的角度讲:函数对一段程序的封装(封装:包装)
站在生活的角度讲:函数就是帮我们完成某样任务的机器
为什么要使用函数
用于包装可以重复使用的代码
代码的复用
输入input和输出output
输入:函数完成任务时必不可少的材料
输出:函数执行完成后产出的产物
定义函数
语法:function 函数名(参数列表) { // code block 代码块 }
function sayHello() {
console.log('hello');
}
调用(call)函数,通过函数名进行调用,并且后面跟上圆括号
sayHello()
有参数的函数:
参数列表中声明参数,如果有多个参数,用逗号隔开
参数列表中的参数,我们称为:形式参数
返回结果:函数的返回值结果称为‘’返回值‘’
return还会终止函数内后续代码的执行
函数若没有返回值(也就是没有return)那么函数默认返回 undefined
function add(x, y) {
// 做函数的运算
let result = x + y
// return 终止函数运行并返回一个输出
return result
// 函数若没有返回值(也就是没有return)那么函数默认返回 undefined
}
调用有参参数:通过函数名调用,且调用的同时,传入真实的参数
调用函数时,圆括号中的参数叫做:实际参数
通过变量(或常量)存储函数和调用函数
声明一个sub变量来储存减法函数
let sub = function (x, y) {
let result = x - y
return result
}
r = sub(1, 2)
console.log(r);
定义参数时,可以给参数设置默认值
当不给函数提供参数时,参数将采用默认值
// 定义参数时,可以给参数设置默认值
function sayMessage(msg = 'hello message') {
console.log(msg);
}
sayMessage('this is my param')
// 当不给函数提供参数时,参数msg将采用默认值
sayMessage()
lambda表达式
什么是lambda表达式
是一种用来定义函数的方法
也成为箭头函数
定义一个无参函数
圆括号部分是参数列表
花括号部分是代码块
const sayHello = () => {
console.log('hello');
}
sayHello()
定义只有一个参数的函数
let sayMessage = (msg) => {
console.log(msg);
}
参数只有一个时,圆括号可以省略
sayMessage = msg => {
console.log(msg)
}
sayMessage('hello message')
lambda的返回值
let add = (x, y) => {
return x + y
}
在箭头后不写花括号,代表直接返回箭头后的内容
add = (x, y) => x + y
console.log(add(1, 2));
返回对象的情况
在箭头后使用圆括号包裹想要返回的对象
let getUser = () => ({ name: '张三', sex: 'male' })
console.log(getUser());
预编译(面试)
参考:JS----预编译及变量提升详解 - 掘金
预编译是什么
在浏览器执行js脚本前需要进行编译,有些代码在编译前会率先被翻译执行并存入内存,这个执行过程称为预编译
总结
哪些东西会被预编译:
1. 调用函数时,在函数执行前会进行函数的预编译
2. 预编译时,函数内的 形参和函数内声明的变量和函数会被预编译
预编译的顺序:
1. 寻找函数内的形参和变量,为其赋值undefined
2. 将实参传值给形参
3. 寻找函数声明
案例
// method(1, 2, 3)
function method(x, y, z) {
console.log(x) // => function x(){}
console.log(y) // => 2
console.log(z) // => function z(){}
var y = 0
console.log(y) // => 0
function z() { }
console.log(z) // => function z() { }
z = 10
console.log(z) // => 10
function x() { }
console.log(x) // => function x() { }
}
method2(true, function (x, y) {
console.log(x);
console.log(y);
}, 'hello')
// let AO = {
// a: function a() { },
// b: function (x, y) { },
// c: 123,
// d: 789
// }
function method2(a, b, c) {
console.log(a); // => function a(){}
function a() {
console.log('this is a');
}
console.log(b(4, 5)); // => 4
// => 5
// => undefined
console.log(c); // => hello
var c = 123
console.log(d); // => undefined
var d = 789
console.log(c); // => 123
console.log(d); // => 789
}
*变量的作用域
什么是变量的作用域(scope)?
作用域指的是作用范围,范围内可以访问变量,超出范围则无法访问
可以结合debugger和浏览器的堆栈信息来查看
方法:
- 在需要查看变量作用的地方写上 debugger
2. 浏览器上 ctrl + shift + i 或 F12
3. 选择 sources
4. 找到当前代码对应的 Call Stack
5. 查看当前 Call Stack 下的 Scope
作用域一个分为三种
-
Global全局作用域
-
Block块级作用域
-
Function作用域
全局作用域
具备全局作用域的只有全局变量或常量
全局变量指的是作用域为全局(Global)的变量,在代码任何位置都可以使用
-
自动全局变量
-
给未定义的变量直接赋值,该变量会变成自动全局变量,若在浏览器中,该变量会存到window对象里
zhangSan = { name: '法外狂徒', age: 17 }
-
-
(普通的)全局变量
-
直接在函数外声明的变量也是全局作用域的变量
let luoXiang = { name: ‘罗老师’ }
var laoWang = { name: '隔壁老王' }
-
块级作用域(Block)
块级作用域就是在代码块中有效的作用域
let关键字声明的变量具备块级作用域
if (true) {
// 块级作用域就是在代码块中有效的作用域
// let 关键字声明的变量具备块级作用域
let a = 1
var other = 2
console.log(a);
console.log(other);
}
// console.log(a);
console.log(other);
函数作用域(Function)
函数中的变量,无论使用var还是let定义的,都是函数作用域,在函数外无法访问
fn()
function fn() {
let x = 1
var y = 2
z = 3
console.log(x);
console.log(y);
console.log(z);
}
// console.log(x);
// console.log(y);
console.log(z);
总结
-
函数外声明的变量 作用域是全局
-
函数内用关键字声明的变量 作用域是函数内
-
let 声明的变量具备块级作用域
深入理解变量的作用域
参考:JavaScript Scope
总结
- 作用域分三类
- Block scope (代码块作用域)
- Function scope (函数作用域)
- Global scope (全局作用域)
-
一个用var或let定义在函数外的变量就是全局变量,全局变量作用域是全局作用域
-
js中对象名和函数名也做变量使用
-
若你给一个没有声明的变量赋值,它会自动变成一个全局变量。(但在js严格模式下不会)
-
在 HTML 中,全局作用域是 window 对象。所有不是使用let定义的全局变量都属于 window 对象(例如有个全局变量 a,那么可以使用 window.a 来访问它)
内存堆栈与垃圾回收
什么是内存堆栈
调用函数时,函数中的各种变量将存储在内存中,以堆栈的形式进行保存
** 堆 **用于存储对象数据的内存区域
** 栈 **指的是一种先进后出的数据格式 用于存储每次函数调用时产生的数据
栈中的每一次调用内容,称为一个栈帧
let a = (x, y) => {
let s = 0
return b(x)
}
let b = (x) => {
let num1 = 1
let num2 = 2
let obj = {name: 'abc'}
return c(num1, num2)
}
let c = (x, y) => {
let sum = x + y
return sum
}
let result = a(2, 3)
console.log(result)
总结:
-
查看堆栈的方法:可以在代码中加入debugger,然后再浏览器控制台中的Call Stack中查看
-
栈将在调用函数时创建数据并进入栈,这个过程称为入栈
-
函数调用结束后将函数中的数据将从栈中移除,这个过程称为出栈
-
出栈时候,释放内存中已经无用的数据的过程称为垃圾回收
-
函数调用时栈的存放顺序始终是先进后出
* 参数的值传递和引用传递
let x = 1
let y = { name: '张三', sex: 'male' }
function modify(a, b) {
a++
b.name = '李四'
b.sex = 'female'
console.log('函数内');
console.log(a);
console.log(
modify(x, y)
console.log('函数调用完后');
console.log(x);
console.log(y);b);
}
传入两个参数:
x是值类型传参
y是引用类型传参
值类型传参:
-
将x变量中的值 直接赋予了函数内的形参
-
boolean number string 都是值传递的
引用类型传参:
-
将y中的引用地址,传递给函数内的形参
-
object function 都是引用传递的
bind call apply函数的应用
函数内可以使用 this 关键字,不同的地方 this 关键字的指代不一样
function fn(a, b) {
console.log(this);
return a + b
}
fn()
函数内的 this 所指代的内容可以通过 bind call apply 来修改
bind 绑定函数的 this 指代
语法 function.bind(thisArg, x, y, …)
thisArg: 要修改的this的指代
x, y, … : 函数的参数,选填
返回值: 一个被修改了this指代后的新函数
let fn2 = fn.bind('hello bind', 1, 2)
console.log(fn2(4, 5));
call 调用函数并指定 this 指代
语法: function.call(thisArg, x, y, …)
thisArg: 要修改的this的指代
x, y, … : 函数的参数,选填
返回值: 函数本身的返回值
console.log(fn.call('hello call', 5, 6))
apply 调用函数并指定 this 指代
语法: function.apply(thisArg, [x, y, …])
thisArg: 要修改的this的指代
x, y, … : 函数的参数,选填
返回值: 函数本身的返回值
console.log(fn.apply('hello apply', [8, 9]))
arguments变量
什么是arguments
用function定义的函数中arguments代表参数数组,是浏览器隐式声明的变量
lambda表达式中不能使用arguments
-
arguments返回的是一个数组
-
arguments.callee 返回函数自身
function add(a, b) { console.log(arguments); // 第一个参数 console.log(arguments[0]); // 第二个参数 console.log(arguments[1]); // 当前函数自身 console.log(arguments.callee) return a + b } add(1,2)
通过arguments.callee时点击事件点击移除事件监听程序
let btn = document.querySelector('button')
btn.addEventListener('click', function () {
console.log('click');
// 移除事件监听程序
btn.removeEventListener('click', arguments.callee)
})
通过arguments.callee时点击事件点击递归匿名函数
let other = document.querySelector('.other')
let count = 0
other.addEventListener('click', function () {
console.log(count);
count++
// arguments.callee 再匿名函数中递归时使用
if (count < 10) arguments.callee()
})
节流和防抖
防抖
-
函数将在一个固定时间后调用,若计时未完成又执行该函数,则取消上次计时,重新开始计时
-
用于限制频繁的网络请求,例如:搜索功能,用户停止输入的一段时间后才会执行搜索任务
防抖步骤
-
1 取消计时
-
2 重新计时
const fd = (() => { // 计时器id let timerId return () => { // 取消计时 clearTimeout(timerId) // 重新计时 timerId = setTimeout(() => { // 计时完成后要执行的代码 写在此处 console.log('hello world'); }, 3000) } })() console.log(fd);
封装一个防抖函数
这样任意函数都可以被添加防抖功能
参数
-
fn:要添加防抖的函数
-
delay:防抖延迟多久
-
that:防抖函数内的this指代
// 封装防抖函数 // 这样任何函数都能被添加防抖功能 function add(x, y) { console.log('ok'); console.log(x, y); return x + y } // 定义一个函数来封装防抖 // fn: 要添加防抖的函数 // delay: 防抖延迟多久 // that: 防抖函数内的 this 指代 function fdPlus(fn, delay, that) { let timerId return function() { // 清除计时器 clearTimeout(timerId) // 重新计时 timerId = setTimeout(() => { // 调用参数fn函数 // 将当前函数的参数 arguments 作为 fn 的参数传入进去 fn.apply(that, arguments) }, delay) } } add = fdPlus(add, 3000, 'helloworld') add(1, 2)
节流
固定时间内只能调用一次的函数,可以使用时间戳或计时器的方式实现
时间戳的方式(了解):
function jlTimespan() {
// 记录最后一次调用该函数的时间
let lastTime
// 函数调用cd
let cd = 3000
return () => {
// 一次都没调用过该函数
if (!lastTime) {
// 执行节流函数的内容
console.log('hello 节流')
lastTime = Date.now()
} else {
// 计算上一次调用该函数和本次调用该函数的时间间隔
// 当前时间
let now = Date.now()
// 获取间隔时间
let timespan = now - lastTime
// 当间隔时间大于cd,则认为可以再次调用该函数
if (timespan >= cd) {
console.log('hello 节流')
// 记录本次调用的时间
lastTime = now
}
}
}
}
let fn = jlTimespan()
console.log(fn);
计时器的方式节流
// 计时器
function jlTimer() {
// 计时器id
let timerId
let cd = 3000
return () => {
// 若已经开始计时 则timerId 存在
if (timerId) return
// 执行被节流的函数内容
console.log('hello 节流');
// 开始计时
timerId = setTimeout(() => {
// 清空计时器id 允许再次调用
timerId = undefined
}, cd)
}
}
fn = jlTimer()
封装一个节流函数
// 封装节流函数
function sub(x, y) {
console.log(this);
console.log('sub');
console.log(x, y);
return x - y
}
// 定义一个函数来封装节流
// fn: 要添加防抖的函数
// delay: 节流的cd
// that: 节流函数内的 this 指代
function jlPlus(fn, delay, that) {
// 计时器id
let timerId
return function() {
if (timerId) return
// 执行节流的代码
fn.apply(that, arguments)
// 计时
timerId = setTimeout(() => {
timerId = undefined
}, delay)
字符串操作
字符串的基本操作
字符串可以视为字符数组
-
查看字符串长度 str.length
console.log(str.length);
-
通过索引访问字符串中的字符str[]
console.log(str[4]);
-
获取指定索引处的字符charAt() 等价于str()
console.log(str.charAt(4));
-
分割字符串split
-
参数用于分割字符串的字符
-
返回值:字符串数组
let r = str.split(' ') console.log(r);
-
-
split+join 替换字符
// 例如:替换字符 *|& 为 _ str = 'hello*|&world*|&!!!' r = str.split('*|&') r = r.join('_') console.log(r);
-
trim:去掉字符串首尾空格
str = ' hello world !!! ' console.log(str); r = str.trim() console.log(r);
-
substring:截取子字符串
-
第一个参数:截取子字符串的起始索引位置
-
第一二个参数:截取子字符串的结束索引位置
-
口诀:前截后不截
-
返回值:截取出来的子字符串
str = ‘hello world !!!’
r = str.substring(4, 9) console.log(r);
-
第二个参数可以省略,如果只写一个参数,substring将从该参数位置一直截取到字符串末尾
r = str.substring(6)
-
-
indexOf:查询字符串中指定字符再字符串中的索引位置
-
参数:要查询的字符串
-
返回值:被查询字符串的索引
console.log(str.indexOf('o'));
-
-
lastIndexOf
console.log(str.lastIndexOf('o'))
-
startWith:用于判断字符串是否以指定字符串开头
-
参数:指定开头的字符串
-
返回值:Boolean值,true代表是以指定字符串开头,false代表不是
console.log(str.startsWith('hello'));
-
-
endWith:用于判断字符串是否以指定字符串结尾
-
toUpperCase toLowerCase 将字符串的英文转成全为大写或小写
-
例如: 统计一个字符串中出现了多少个a字符,忽略大小写
str = 'alhdAkdjfalKHgladhfdjAhg' str = str.toLowerCase() let count = 0 for (let i = 0; i < str.length; i++) { const char = str[i]; if (char === 'a') count++ } console.log(count);
-
补充 toFixed 保留小数点后多少位的函数
数字操作
参数:指定小数点后保留多少位(结果是四舍五入)
返回值:是一个保留了指定小数点位数的字符串
let num = 3.1415926
r = num.toFixed(3)
console.log(r);
字符串的match函数
从字符串中匹配出符合正则的字符串
参数:用于匹配的正则表达式
返回值:包含了匹配结果的数组
let str = 'goods_gods_goooods'
let regex = /go{1,3}ds/
let r = str.match(regex)
console.log(r);
正则表达式
正则表达式是用于匹配字符串的表达式
声明正则表达式的方法:
-
方法一
let regex = new RegExp('^[0-9a-zA-Z]+@[0-9a-zA-Z]+\.(com|cn)$')
-
方法二
regex = /^[0-9a-zA-Z]+@[0-9a-zA-Z]+\.(com|cn)$/
调用正则表达式test方法 检测字符串是否符合正则表达式
返回值true代表 受测字符串符合正则表达式描述的特征
console.log(regex.test('xyz@gmail.com'));
正则表达式语法
参考:正则表达式_百度百科
-
\反斜杠 :转义
-
^:匹配字符串的开头
-
$:匹配字符串的结尾
匹配字符个数的符号
这些匹配字符个数的符号,代表的意思是:匹配前一个字符多少次
-
*:任意次
-
?: 匹配0次或者1次
-
+:匹配至少1次
-
{n}:匹配指定次数
-
{n,}:匹配至少n次
-
{n,m}:匹配至少n次,至多m次
匹配字符个数的符合
-
x|y : 或
-
[a-z] [0-9] : 取范围值,匹配一个字符,该字符在指定范围内
-
[^5-7]: 取范围负值,匹配一个字符,该字符不在指定范围内
分组 (pattern): 将pattern里面的所有字符当作一个字符处理
-
站在字符串的角度看,圆括号不仅有分组的作用,同时,它将被取值
-
// (?:pattern): 匹配分组内容,但不获取圆括号中的值
预查询
- 预查询:正则表达式会去匹配预查询中的内容,但不会进行取值
- 预查询分方向:前后
assert 断言 https://developer.mozilla.org/zhCN/docs/Web/JavaScript/Guide/Regular_Expressions/Assertions
-
(?=pattern ) :后置预查询
-
先行否定断言
-
语法:x(?=y)
-
x跟随y时 匹配x
-
/小明(?=小红)/ 该正则理解为: 小明后面一定跟随了一个小红
-
-
-
(?!pattern): 负后置预查询
-
后行否定断言
-
语法:x(?!y)
-
x 后没有 y 跟随时 匹配 x
-
/小明(?!小红)/ 小明后面一定没有跟随小红
-
-
-
(?<=pattern) : 前置预查询
-
后行断言
-
语法: (?<=y)x
-
x 的前有 y 则 匹配 x
-
/(?<=小红)小明/ 小明的前面一定有小红
-
-
-
(?<!pattern) 负前置预查询
-
后行否定断言
-
语法: (?<!y)x
-
x 前没有 y 则 匹配 x
-
/(?<!小红)小明/ 小明的前面一定没有小红
-
-
正则表达式中的特殊字符串
-
\d :十进制数
-
\D :非十进制数
-
\r :回车
-
\n :换行
-
\s :所有不可见字符,字符表 回车换行 空格等
-
\S :所有可见字符
-
. :基本等于任意字符,但是不包括\r\n
-
匹配任意字符任意次数的写法: regex = /[\S\s]*/
-
\t :制表符
正则表达式的全局模式
不开全局模式:
只会匹配到第一个符合条件的字符串
全局模式
全局模式的正则表达式将匹配所有符合条件的字符串
在正则表达式的后面使用 g flag来开启全局模式
另外一个flag 是i代表 忽略大小写
matchAll 匹配所有符合正则描述的字符串
参数必须是一个全局模式的正则表达式
返回值:是一个iterator(迭代器)
什么是迭代器?用于循环遍历一个集合的工具就是迭代器
迭代器一定包含一个 next 函数 代表取出下一个成员
所有迭代器都可以使用forof来遍历
// matchAll 匹配所有符合正则描述的字符串
// 参数必须是一个全局模式的正则表达式
regex = /go+ds/g
// 或 使用 RegExp
// 第二个参数就是flag
regex = new RegExp('go+ds', 'g')
let r = str.matchAll(regex)
console.log(r)
// 此处返回值 r 是一个 iterator (迭代器)
// 什么是迭代器?用于循环遍历一个集合的工具就是迭代器
// 迭代器一定包含一个 next 函数 代表取出下一个成员
// 所有的迭代器都可以使用forof来遍历
for (const item of r) {
console.log(item);
}
贪婪模式和非贪婪模式
贪婪模式(默认)
字符串在匹配正则表达式时,将尽可能多的匹配满足正则规则的字符
非贪婪模式
字符串在匹配正则表达式时,将尽可能少的匹配满足正则规则的字符
开启非贪婪模式 在匹配个数的符合后加上’?‘问哈
// 如何开启非贪婪模式
// 方法:在表示匹配个数的符号后面加上'?'问号
regex = /^1234+?/
console.log(str.match(regex));
regex = /^1234*?/
console.log(str.match(regex));
regex = /^1234??/
console.log(str.match(regex));
regex = /^1234{1,4}?/
console.log(str.match(regex));
闭包和计时器
闭包
闭包也叫函数闭包,通过函数产生一个封闭的内存空间,包裹一些需要保存的数据
且函数需要返回一个持续引用的对象,这就叫闭包
// 为了方便理解,做如下的假设:
// 1. 假设有一个函数A
// 2. A内声明变量B
// 3. A返回一个 包含函数的内容
// 4. A返回的 包含的函数必须引用变量B
// 5. 此时函数A就是闭包的
闭包的应用场景
用于储存一些不让函数外访问的数据,或者为了作用域中变量名的冲突,可以使用闭包
const demo = (function A() {
let B = '张三'
return () => {
// 函数内引用变量B 则函数A是闭包的
console.log(B)
}
// 当返回一个值时 函数A将返回 A中没被使用的变量将被垃圾回收掉,则A不是闭包的
// return 'hello world'
// 当返回的是直接声明的对象或数组的时候,声明时将B作为对象属性的值或数组成员传入对象或数组
// 当A返回后,变量B则没有再被引用了,则被垃圾回收掉,A不是闭包的
// return { name: B }
})()
// 使用自调用函数,返回一个函数
const sayMessage = (() => {
// 声明一个用于记录调用次数的变量
// 闭包的函数空间内声明的变量,在内存中将持续存在
// 垃圾回收不会回收该变量
// 该变量在外部无法访问
let count = 0
// 在自调用函数中返回一个函数
// 返回的函数就是一个闭包后的函数
return (msg) => {
console.log(msg)
count++
console.log(`sayMessage 调用次数为 ${count}`);
}
})()
导致内存溢出的情况
内存溢出
当内存中堆栈内容已经装不下了,再给内存增加内容则会导致溢出
栈溢出
无限的函数递归会引发栈溢出
function fn1() {
fn1()
}
// fn1()
堆溢出
// 每次循环都声明一个新的对象
// 对象保存在堆上
// 当堆上的内容过多 则会导致堆溢出
// 堆溢出
let count = 0
while (true) {
// 每次循环都声明一个新的对象
// 对象保存在堆上
// 当堆上的内容过多 则会导致堆溢出
let a = { name: Date.now() }
if (count > 5) {
console.log(count);
}
count++
}
计时器
当经过指定一段时间后触发一段代码的函数就是一个计时器
声明一个计时器:setTimeout
-
参数1:计时器计时结束后触发的函数
-
参数2:计时时长,单位毫秒
-
返回值:计时器id
-
计时器id 用于停止计时
// let timerId = setTimeout(() => { // console.log('hello setTimeout') // }, 3000)
清空计时器:clearTimeout(timerId)
// document.querySelector('.btn1').addEventListener('click', () => {
// // clearTimeout 清空计时器
// // 参数是 计时器id
// // 清空后计时器将取消掉
// clearTimeout(timerId)
// })
循环计时函数 setInterval
每次经过指定时间,触发一次指定的函数
-
参数和返回值与setTimeout相同
// let count = 0 // let timerId2 = setInterval(() => { // count++ // console.log(count); // }, 1000)
清空循环计时函数 clearInterval()
// clearInterval
// document.querySelector('.btn2').addEventListener('click', () => {
// // 清空循环计时器
// clearInterval(timerId2)
// })
注意:请避免出现死循环
注意事项:
- 尽量不使用 setInterval
理由:setInterval 可能由于人为原因忘了关闭,或者内部出现异常,导致代码死循环
-
若要做大量循环调用甚至是无限循环调用时(例如轮询死循环调用),请使用 setInterval 而不是使用 setTimeout 递归进行循环
理由:setTimeout 会占用大量内存堆栈
js的移动和渐入渐出动画
移动元素的思路
使用计时器 每隔一段时间 让元素位置产生一个增量的变化
const box = document.querySelector('.box')
let timerId = setInterval(() => {
// 一个动画帧
// 先运行逻辑运算
// 设一个速度,一帧移动15像素
let v = 15
// 当前的距离值
let distance = getComputedStyle(box).left
distance = Number(distance.substring(0, distance.length - 2))
// 叠加速度后的距离
distance += v
if (distance >= 500) {
// 停止计时器
clearInterval(timerId)
}
// 再渲染到元素上
box.style.left = `${distance}px`
}, 200)
渐入渐出动画
使用setInterval 使透明度有序变化
const img = document.querySelector('img')
let timerId
// 绑定鼠标移入移出事件
// 进入事件
img.addEventListener('mouseenter', () => {
console.log('enter');
clearInterval(timerId)
timerId = setOpacity(img, Number(img.style.opacity), -0.01, 100)
})
// 离开事件
img.addEventListener('mouseleave', () => {
console.log('leave')
clearInterval(timerId)
timerId = setOpacity(img, Number(img.style.opacity), 0.01, 100)
})
// img.addEventListener('mouseover', () => {
// console.log('over');
// })
// img.addEventListener('mouseout', () => {
// console.log('out')
// })
// 设置透明度
// el: 用来修改透明度的元素
// start: 透明动画播放时的初始值
// v: 透明度的变化速度
// duration: 计时器的间隔时间
function setOpacity(el, start, v, duration) {
// 当前透明度
let current = start
// 计时器运行前先赋值一个初始透明度
el.style.opactiy = current
let timerId = setInterval(() => {
// 先计算动画逻辑
current += v
// 判断是否透明度已经到了极限值
// if (current <= 0) {
if ((v < 0 && current <= 0) || (v > 0 && current >= 1)) {
// 停止计时器
clearInterval(timerId)
// 赋值current为极限值
current = v < 0 ? 0 : 1
}
// 后赋值
el.style.opacity = current
}, duration)
// 返回一个计时器id 方便随时停止计时器
return timerId
}
封装一个移动函数
思路
使用计时器播放动画
步骤
-
声明移动函数
-
使用计时器,每帧让元素移动指定速度的距离
-
在每一帧移动后,判断是否达到了终点位置,若已经达到站点位置,就应该停止计时器
-
若最后一帧位置超出了终点位置,应该将元素设置到终点位置上
封装函数
// 函数用来封装不变的内容,变化的内容就成为参数
// 参数:
// el: 需要移动的元素
// v: 速度
// axis: 坐标轴 接收参数 'x'|'y' 代表 x轴和y轴
// distance: 移动距离 填入一个正数
// duration: 每一帧经过的时长
// offset: 移动距离的初始偏移量
function move(el, v, axis, distance, duration, offset) {
// 当前移动距离
let current = 0
let timerId = setInterval(() => {
// 一个动画帧
// 先运行逻辑运算
// 当前的距离值
current += v
// 判断当前距离是否已经大于等于预设的目标距离
// 通过 Math.abs 取 current 的绝对值 和 distance 进行判断
if (Math.abs(current) >= distance) {
clearInterval(timerId)
// current 可能已经大于目标距离
// 所以赋值 distance
// 由于 distance 的值永远是正数,所以需要乘以速度的符号
// Math.sign(v) 作用是获取 v 的符号 返回 1 或 -1
current = Math.sign(v) * distance
}
// 再渲染到元素上
if (axis === 'x')
el.style.left = `${current + offset}px`
else
el.style.top = `${current + offset}px`
}, duration)
}
const box = document.querySelector('.box')
move(box, -10, 'x', 1000, 200, 500)
时间对象和数学函数
时间对象
参考
时间字符串的参考
时间对象Date
表示时间的字符串
字符串需要满足w3c联盟要求的时间字符串格式
例如1990-01-01
表示时间的数字
含义:表示格林威治1970-1-1开始以后,经过的毫秒时间
创建时间对象的方法 new Date()
-
当前系统时间let date = new Date()
-
Date 时间对象默认修改了对象的 toString 方法 所以转字符串时,会显示成时间字符串
-
所有对象都有 toString 方法,转换字符串时,js回调用该方法
-
-
创建指定时间字符串的时间
date = new Date(‘1997-07-07’)
// 不推荐使用,因为这是个非标准时间字符串,不同浏览器解析方式可能不同
-
通过格林威治毫秒时创建时间
- date = new Date(1000 * 60 * 60 * 24 * 365)
-
通过年月日时分秒创建时间
-
date = new Date(2000, 5, 6, 18, 44, 22)
-
参数分别代表 年月日时分秒
-
注意:月份是从0开始计算的
-
参数至少写前两个参数,后续参数可以省略
-
读取时间
-
date = new Date()
-
console.log(date.getFullYear()); // 年
-
console.log(date.getMonth()); // 月 月份从0开始计算
-
console.log(date.getDate()); // 日 一个月中的第几天
-
console.log(date.getDay()); // 一周中的第几天 一周中的第一天是周日值为 0
-
console.log(date.getHours()); // 时
-
console.log(date.getMinutes()); // 分
-
console.log(date.getSeconds()); // 秒
-
console.log(date.getMilliseconds()) // 毫秒
设置时间
date.setFullYear(2023)
-
date.setMonth(0)
-
date.setMonth(0)
-
date.setDate(5)
-
date.setHours(20)
-
date.setMinutes(66)
-
date.setSeconds(66)
-
date.setMilliseconds(1000)
Date.now() // 获取当前系统时间的格林威治毫秒时
date.getTime() // 获取date对象代表的格林威治毫秒时
通常来说日期对象需要转换成字符串显示,否则用户看不懂
function format(date) {
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}`
}
console.log(format(new Date()));
数学函数
专门处理数学运算的函数
数学函数可以通过浏览器内置对象Math进行直接调用
参考:
Math - JavaScript | MDN
三角函数
需要注意的是,三角函数 sin()、cos()、tan()、asin()、acos()、atan() 和 atan2() 返回的值是弧度而非角度。
若要转换,弧度除以 (Math.PI / 180) 即可转换为角度,同理,角度乘以这个数则能转换为弧度。
三角函数中所有和角度相关值都是弧度制
// 例如:计算30度的正弦值,如下:
console.log(Math.sin((Math.PI / 180) * 30));
// 反三角函数就在三角函数前加上a就可以了
console.log(Math.asin(0.5) / (Math.PI / 180));
abs:获取数字的绝对值
ceil:小数向上取整
floor:向下取整
round:四舍五入
max:取参数中较大数
min:取参数中较小数
通过max和min写一个定界函数
value:要定界的数字
max,min 最大最小值
返回值,value < min时返回min value > max时返回max value在min和max之间时 返回value
function clamp(value, min, max) {
// let temp = Math.min(value, max)
// temp = Math.max(temp, min)
return Math.max(Math.min(value, max), min)
}
pow:返回x的y次方 Math.pow(x,y)
sqrt:返回一个数的平方根
random : 取随机数,范围在(0-1)之间,能取0取不到1
sign:取符号
事件和异常处理
事件
在在js中,事件就是:当某种情况发生的时候,能够触发一段代码,这个发生的情况就是事件
简单的理解,事件就是:(当 。。。。时;就。。。。样)
事件在js中以对象形式存在
什么是绑定事件?
将自定义的函数和某个事件进行捆绑,绑定后的效果就是:(当。。。时,发生的时候,就会触发我们绑定的函数)
绑定事件的方法
使用元素对象的addEventListener函数进行事件绑定
EventTarget.addEventListener() - Web API 接口参考 | MDN
addEventListener绑定事件
-
第一个参数:要绑定的事件名称
-
第二个参数:当事件发生时,触发的函数
-
第二个参数的函数,不是有开发人员调用的,当事件发生时由浏览器调用的
const btn1 = document.querySelector('.btn1')
// 声明一个事件处理程序
function btn1Clicked(event) {
// event 是触发的事件对象
console.log(event);
// this 关键字代表被添加该事件处理程序的dom对象
console.log(this);
}
// 给btn1添加事件监听器
btn1.addEventListener('click', btn1Clicked)
// 解绑事件
const btn2 = document.querySelector('.btn2')
btn2.addEventListener('click', () => {
// 移出事件监听器
// 调用removeEventListener来解绑事件
// 参数一:要解绑的事件名称
// 参数二:要解绑的事件处理程序
btn1.removeEventListener('click', btn1Clicked)
})
// 我们可以使用元素上带有on开头的属性进行事件绑定,例如onclick:
btn1.onclick = () => { console.log('按钮被点击了') }
// 解绑onclick函数,如下:
// btn1.onclick = null
function onClick() {
console.log('按钮被点击了')
}
事件的分类
-
资源事件
-
load 加载完成
-
error 加载失败
-
-
焦点事件
-
focus 获取焦点
-
blur 失去焦点
-
-
鼠标事件
-
click 点击事件
-
contextmenu 右键菜单
-
dblclick 双击
-
mousedown 鼠标点下
-
mouseup 抬起
-
mouseenter 进入
-
mouseleave 离开
-
mouseover 悬停
-
mouseout 出去
-
mousemove 移动
-
wheel 滚轮
-
drag拖动事件
-
-
元素上要添加 draggable = ‘true’
-
dragstart 开始拖动
-
dragend 结束拖动
-
-
-
媒体事件:和媒体播放器相关事件,详细查文档
-
表单元素事件
-
input 输入事件
-
change 变化事件
-
-
按键事件
-
keydown 按下
-
keyup 抬起
-
keypress 按压
-
事件机制------冒泡
在html中触发事件的元素,将会把事件不断的向父元素传递,这个过程叫做事件冒泡
document.querySelector('button').addEventListener('click', () => {
console.log('button');
})
document.querySelector('div').addEventListener('click', () => {
console.log('div');
})
document.body.addEventListener('click', () => {
console.log('body');
})
捕获事件(事件的捕获)
事件触发后,可以由上级元素先处理事件,这个过程就是捕获事件
addEventListener 的第三个参数代表是否捕获事件
捕获事件的元素会先处理事件,然后将事件对象还给产生事件的元素,然后正常冒泡
document.body.addEventListener('click', () => {
console.log('body');
}, true)
阻止事件冒泡-----使用事件对象的 stopPropagation 来阻止事件冒泡
ev.stopPropagation()
屏蔽默认事件
方法一:使用事件对象屏蔽元素事件的默认行为
document.querySelector('button').addEventListener('click', ev => {
// preventDefault 屏蔽当前元素默认的事件行为
ev.preventDefault()
console.log('提交');
})
// document.querySelector('.box').addEventListener('contextmenu', ev => {
// ev.preventDefault()
// console.log('右键');
// })
方法二:对应事件属性 retrun false 也能屏蔽默认行为
<div class="box" oncontextmenu="return false"></div>
事件对象的currentTarget 和target
targrt:代表产生事件的元素
currentTarget:当前处理事件的元素
注意:currentTarget是变化的
button.addEventListener('click', function (ev) {
console.log('button');
console.log(ev.target);
console.log(ev.currentTarget);
// this 代表当前处理该事件的元素
console.log(this === ev.currentTarget);
})
div.addEventListener('click', function (ev) {
console.log('div');
console.log(ev.target);
console.log(ev.currentTarget);
// this 代表当前处理该事件的元素
console.log(this === ev.currentTarget);
})
通过js代码来触发元素事件的方法
方法一: 通过对应事件名的函数进行触发
缺点: 不是所有事件都有对应的方法来触发
document.querySelector('.btn1').addEventListener('click', () => {
console.dir(box)
// 调用 click 来触发点击事件
box.click()
})
方法二:发出事件对象
步骤:
// 1. 创建事件对象
// 2. 调用要接收该事件的dom的dispatchEvent方法
document.querySelector('.btn2').addEventListener('click', () => {
// 事件对象Event的文档地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Event/Event
// 1. 创建事件对象
// 第一个参数: 事件名
// 第二个参数: 配置对象
let event = new Event('contextmenu', {
// 配置项 如下
// 是否允许事件冒泡
bubbles: false,
// 是否允许取消事件
cancelable: true
})
// 2. 调用要接收该事件的dom的dispatchEvent方法
// dispatchEvent 用来派发一个事件
// 返回值是个布尔值,true 代表事件没被取消 false 代表事件被取消(调用了 preventDefault)
let r = box.dispatchEvent(event)
console.log(r);
})
自定义事件对象
发出自定义事件的方法:
-
给需要接收自定义事件的元素,绑定自定义事件
-
创建一个自定义事件
-
调用需要接收自定义事件的dom的dispatchEvent
// 1. 给需要接收自定义事件的元素,绑定自定义事件
const box = document.querySelector('.box') document.body.addEventListener('open-my-eyes', ev => { console.log('body my eyes open'); console.log(ev); }) box.addEventListener('open-my-eyes', ev => { ev.preventDefault() console.log('box my eyes open'); console.log(ev); })
document.querySelector('button').addEventListener('click', () => {
// 2. 创建一个自定义事件
// CustomEvent 参数和 Event 的参数基本相同
const ce = new CustomEvent('open-my-eyes', {
bubbles: false,
cancelable: false,
// 自定义的事件参数
// 该参数可以传递任意内容
detail: { name: '张三' }
})
给div添加tableindex,使div成为一个表单元素,实现获取焦点
在div上加入一个tabindex,可以让div成为一个表单元素
tabindex 可以指定使用tab按键切换焦点元素时的顺序,顺序由大到小排列
<div tabindex="0"></div>
<input tabindex="1"/>
let div = document.querySelector('div')
div.addEventListener('keydown', ev => {
console.log('keydown')
}, true)
div.addEventListener('focus', ev => {
console.log('focus')
})
异常处理
异常处理的基本处理方法和异常的特点
异常:
当程序运行时,运行不下去了,碰到了问题,该问题就是一个异常异常的特点:当程序抛出异常后,代码将停止运行
异常处理
当程序出现异常时,开发人员进行的一个手动处理 异常处理的方法叫做捕获异常
只有运行时异常(runtime error)需要进行捕获
运行时异常:程序稳定运行时,可能出现的异常(例如用户输入)
异常演示
function fn1() {
fn2()
}
function fn2() {
// 当程序遇到问题时,由js引擎自动创建一个异常对象,从函数内向外抛出
// 异常出现在函数中
console.log(obj)
}
// 异常抛出到调用栈的顶部之后,还没人处理,则异常将抛到控制台,程序将终止运行
// fn1()
通过 try catch 捕获异常,手动排除异常并让程序能够继续运行
语法
// try {
// // 代码块: 该代码块中可以写入你想要尝试运行的代码
// } catch(e) { // e:该参数是一个 Error 对象
// 当 try 代码块中的容抛出异常时,将触发 catch 代码块的内容
// // 代码块: 对异常进行处理的逻辑写在这里
// }
// 注意:try catch 代码块 必须同时存在
// try {
// // 在try中执行可能会有异常的代码
// fn1()
// } catch (e) {
// // Error 对象包含两个常用属性
// // message: 异常消息
// console.log(e.message);
// // stack: 调用栈的信息
// // 调用栈可以用来查看异常程序整个调用的流程,便于找到异常位置
// console.log(e.stack);
// }
// 被处理后的异常不会导致程序终止
// 程序会继续执行
// console.log('hello world');
Error:异常对象
当程序出现了系统异常时,程序会自动抛出一个Error对象
可以手动创建异常对象
function div(x, y) {
if (y === 0) {
// 除数不能为零,所以此处我们手动抛出异常
// 1. 创建异常对象
// let error = new Error('除数不能为0')
// // 2. 抛出异常
// // 抛出异常后程序将终止运行
// throw error
throw new Error('除数不能为0')
}
return x / y
}
try {
div(2, 0)
} catch (e) {
console.error(e);
}
console.log('hello world');
finally
finally是try catch 之后的一个代码块,作用是,无论try中是否出现异常,finally 中的代码都会执行
function div(x, y) {
if (y === 0) {
// 除数不能为零,所以此处我们手动抛出异常
// 1. 创建异常对象
let error = new Error('除数不能为0')
// 2. 抛出异常
// 抛出异常后程序将终止运行
throw error
}
return x / y
}
try {
let r = div(3, 1)
console.log(r);
} catch (e) {
console.error(e);
} finally {
// try catch 后 最终一定会执行的程序
console.log('无论try执行成功还是抛出异常,都会执行此处的代码');
}
自定义异常对象
开发人员自己创建的异常类就是自定义异常
作用
给异常分类,告诉开发人员哪些异常是需要处理的,哪些是系统异常不需要处理
// 除法
function div(x, y) {
// 参数验证
// if (isNaN(x) || isNaN(y)) throw new Error('参数不是数字')
if (isNaN(x) || isNaN(y)) throw new NaNError()
if (y === 0) {
throw new ArgZeroError()
}
return x / y
}
自定义异常类
// 自定义异常类
// 参数不是数字异常
class NaNError extends Error {
// 使用 new 关键字创建类实例时,调用 constructor 构造函数
constructor() {
// 父类构造函数
super('参数不是数字')
}
}
class ArgZeroError extends Error {
constructor() {
super('除数不能为0')
}
}
try {
div('ab7c', 0)
} catch (e) {
// 处理异常
// 判断异常的类型 分别进行处理
// n instanceof m 含义为: n 是否是 m 类型的实例
if (e instanceof NaNError) {
console.error('参数异常');
} else if (e instanceof ArgZeroError) {
console.error('参数为0');
} else {
console.error(e);
}
}
面向对象编程
面向过程编程和面向对象编程
面向过程编程:
根据功能逻辑,按照功能和代码的执行顺序编写代码,且以计算机的运行逻辑按照1234的步骤书写的代码,这就是面向过程的编程方法
面向对象编程:
面向对象编程(OOP:Object Oriented Programming):站在现实生活的角度,将计算机中的抽象的物体当作一个具体的东西(这个东西就叫做对象Object)来处理,这就是面向对象编程的思想
对象
js用属性和行为来描述某个物体而产生的一种数据结构,该数据称为对象
对象的静态属性和方法
静态属性和方法 指的就是一类中共有的属性和方法
class 类中 用static关键字声明静态属性和方法
class Human {
name
sex
// class 类中 用static关键字声明静态属性和方法
// 静态属性是属于类的属性
// 用来描述这个类
static category = '哺乳动物'
constructor(name, sex) {
this.name = name
this.sex = sex
}
eat(food) {
console.log(`${this.name} 吃了 ${food}`);
}
// 静态方法,用来描述一个类的行为
static greedy() {
console.log('小孩子才做选择 我全都要');
}
}
let human = new Human('张三', 'male')
// 调用静态属性或方法,使用类名调用而不是实例对象
console.log(Human.category);
Human.greedy()
function声明的类
不能使用 static 关键字声明静态属性和方法的
只能通过给构造函数添加属性来充当静态属性和方法
// function 类 是不能使用 static 关键字声明静态属性和方法的
// function 类 只能通过给构造函数添加属性来充当静态属性和方法
function Car() { }
Car.category = '工业制品'
Car.jiaYou = function () {
console.log('汽车要加油');
}
console.log(Car.category);
Car.jiaYou()
JSON对象
json 对象是js中的对象,且该对象的格式采用json的格式(js中也称为朴素对象 plain object)
json JavaScript Object Notation (js 对象简谱)
json是一种js的轻量化交互式数据
json的格式:
-
根节点只能有一个
-
json中用 “key”: value 的方式定义属性,多个属性用逗号隔开
-
属性名必须用双引号包裹
-
属性值若为字符串,字符串必须用双引号包裹
json 对象
json 对象采用json格式,但可以有一些省略的写法
写法:
let human = {
"name": "张三",
"age": 16,
"是否已婚": false,
"company": {
"name": "hqyj"
}
}
js中可以的写法:
let human = {
'name': '张三', // 字符串可以用单引号
age: 16, // 若属性名不包含特数字符则可以省略引号
"是否已婚": false,
"company": {
"name": "hqyj"
}
}
若属性名和属性值的变量名相同则可以省略冒号后面的内容,如:
let name = '张三'
let zhangSan = {
// name: name // 完整写法
name // 省略后的写法
}
若在json对象中声明函数,那么可以省略function关键字或lamda表达式的箭头
let a = {
// 完整写法如下
// fn: ()=>{
// console.log('fn被调用了')
// }
// 省略写法
fn() {
console.log('fn被调用了')
}
}
类和实例对象
类
用于描述同类事物的类型叫做类,类型是抽象的
构造函数
用于构造类实例的函数
-
es5
-
// 声明类型 // es5 // 类型名大写开头 // 该函数 Human 就是类型的构造函数 function Human(name, age, sex){ // 在类型中 this 关键字 代表当前实例对象 // 属性 this.name = name this.age = age this.sex = sex // 行为 this.eat = function(food){ // 方法中使用this访问实例对象 console.log(`${this.name} 正在吃 ${food}`); } }
-
es6
// es6 语法定义类型如下: class Car { // 属性 // 价格 value = 0 // 生产厂商 producer = '' // 名称 name = '' // 声明构造函数 constructor(value, producer, name){ this.value = value this.producer = producer this.name = name } // 行为 run(){ console.log(`价值 ${this.value} 的 ${this.name} 在跑`); } }
实例化类型
生成一个类型的个例的过程称为实例化
以下代码中 new Human() 这句话就是在实例化Human类
实例化的结果被存在了 zhangSan 变量中,
我们称实例化的结果为:实例,实例对象,类实例
new 关键字用于实例化类型
new 关键字后面的 Human() 实际上是在调用构造函数
let zhangSan = new Human('张三', 17, 'male')
console.log(zhangSan);
实例和类的关系
类是实例的模板,实例是类的成品
// 所有使用 constructor 构造函数构造而成的数据类型都是 Object 对象类型(也就是使用关键字 new 创建的对象)
// 例如:
// console.log(typeof Number(123))
// console.log(typeof new Number(123))
// console.log(typeof String('hello'))
// console.log(typeof new String('hello'))
// 这可以解释为什么数组是 object 类型
// 因为数组可以使用 new Array() 来创建
判断对象是否包含某个key
-
let obj = { a: 1, b: 2, c: ‘’ };
// 方法一: 使用 boolean 值的隐式转换 // if (obj.c) { // // 若存在就执行if里的代码块 // console.log('ok'); // } // 甚至可以用来判断对象是否存在 // if (obj) { // console.log('存在'); // } // 上述方法的缺点是: 若 c 的值是空字符串,0或者false 都会被认为 “c 不存在”,然而 c 这个 key 是存在的
-
// 方法二: 使用 in 关键字判断
// 判断 c 这个 key 是否存在于 obj 对象中 // if ('c' in obj) { // console.log('c 存在'); // }
-
// 方法三: 使用 Reflect 反射
// if (Reflect.has(obj, 'c')) { // console.log('c 存在'); // }
-
// 方法四: 直接通过类型判断是否存在 if (typeof obj.c !== 'string') { console.log('c 存在'); } // 判断不是 null 和 undefined if (typeof obj.c !== 'null' && typeof obj.c !== 'undefined') { console.log('c 存在'); } // 上述方法的缺点是: 必须要预先知道 c 的类型才可以进行检测
-
// 总结:
// 1. 绝对不会错的用法是 方法二 和 方法三
// 2. 快速开发使用 方法一 但要注意其缺点
声明私有属性和私有方法
在实例对象内能使用但是实例对象外无法使用的属性和方法,称为私有属性和方法
私有方法或属性在名称的前面使用 # 井号
this,super,严格模式
this
this关键字不同的场景,this关键字代表的东西不同
-
js执行上下文中的js =>window
console.log(this); // => window console.log(this === window);
-
函数内的this,非严格模式下 函数内 this => window
// 函数内的this
function fn() {
// 非严格模式下 函数内 this => window
console.log(this);
}
fn()
-
json对象方法中的this=>对象自己
let obj = { name: '张三', fn() { console.log(this); // => 对象自己 } } obj.fn()
-
实例对象方法的this
// 类方法中的 this => 调用该方
class A { name constructor(name) { this.name = name } fn() { // 类方法中的 this => 调用该方法的实例对象 console.log(this); } } let a = new A('a') let b = new A('b') a.fn() b.fn()
-
有些情况下,回调函数中的this可能被其他函数赋值,所以this不是undefined
此处 事件回调函数被 addEventListener 赋值了其中的 this
所以这里的this指的是绑定事件的那个dom对象
document.querySelector('button').addEventListener('click', function () {
console.log(this);
})
总结:
-
js 执行上下文 => window
-
函数内 this => window (函数=>window)
-
方法内的this指向调用方法的对象 (方法=>实例)
-
this 可能被其他函数赋值 例如 addEventListener 这种时候 this 既不是 window 也不是 undefined,它取决于函数 addEventListener 自己
lambda表达式的this
结论
-
lambda 表达式没有 this
-
lambda 表达式中的 this 来自于声明函数时上下文中的 this
-
作用:
-
利用好 lambda 表达式没有 this 的特点,灵活的使用 this 获取想要的上下文中的内容
lambda表达式让回调函数访问实例对象
// lambda 表达式中 this 用处:
// 让回调函数能够访问实例对象
class B {
fn() {
// 有时在实例对象的方法中可能会调用一个需要传入回调函数的函数
// 例如此处的 custom
// 假设希望在 custom 的回调函数中 调用 play 方法 该怎么办?
// 方法一:
// 使用 function 声明回调函数
// 在调用 custom 之前,用变量 that 存储B实例 this
// let that = this
// custom(function () {
// that.play()
// })
// 方法二:
// 使用 lambda 表达式声明回调函数,则 lambda 表达式内 this 就是B实例
custom(() => {
this.play()
})
}
play() {
console.log('B在玩');
}
}
function custom(callback) {
callback()
}
let b = new B()
b.fn()
instanceof 检测对象是否是某个类型的实例
-
语法: n instanceof M
-
含义: n 是否是 M 类的实例,如果是 则表达式返回 true
继承与原型
继承:
从父类传承下他的属性和行为,使子类具备相同的属性和行为,同时子类可以拥有自己的属性和行为
父类(parent class)也叫做基类(base class),也叫超类(super class)
extends继承
// 使用 extends 关键继承父类
// 欧洲人
class Eurapean extends Human {
// 子类可以有自己的属性和行为
luck = true
detox() {
console.log(`远离黄赌毒 珍爱生命`);
}
// 子类可以拥有和父类相同的方法,用来覆盖父类的方法
// 这种方法称为 方法的重写 (override)
sleep() {
console.log(`${this.name} 在睡觉`);
}
}
let h = new Eurapean()
h.name = '张三'
h.sex = 'male'
console.log(h);
h.sleep()
console.log(h.luck);
h.detox()
// 非洲人
class Afriaca extends Human {
}
+ ###### function语法下的继承
```js
// function 类 语法的继承
// 汽车
function Car() {
this.name = '皮卡'
this.run = function () {
console.log('车在跑');
}
}
// 声明一个皮卡继承Car
function PickCar() {
// 载重量
this.weight
this.mount = function () {
console.log('装货');
}
}
-
利用原型进行继承
// 利用原型进行继承 // 什么是原型? 类的模板(设计图) // 给 PickCar类型赋值原型属性,既能进行继承 PickCar.prototype = new Car() let pk = new PickCar() console.log(pk);
// 原型 __proto__ 和 prototype 的区别
// __proto__ 实例对象中访问原型的属性
// prototype 类访问原型的属性
// 实例对象才能访问 __proto__ 对象
console.log(pk.__proto__);
// 类名才能访问 prototype
console.log(PickCar.prototype);
// 原型属性或原型函数
// 例如
// Car.prototype.wheelCount = 4
// Car.prototype.shout = () => {
// console.log('喇叭叫')
// }
// // 原型属性或原型函数可以在类实例或子类的实例中调用
// // 实例化对象会继承原型属性和函数
// let c = new Car()
// console.log(c.wheelCount);
// c.shout()
// pk = new PickCar()
// console.log(pk.wheelCount);
// pk.shout()
// // 若不实例化类,可以通过原型,直接访问,例如:
// console.log(Car.prototype.wheelCount)
// Car.prototype.shout()
### super关键字
子类构造函数中理论上都应该使用 super 关键字来构造父类
在构造函数中,super 代表父类的构造函数
子类构造函数要使用 this 关键字的话,则必须先调用 super
可以使用super关键字调用父类方法
方法中的super代表父类实例
```js
class Duck extends Bird {
color
constructor(name, color) {
// 子类构造函数中理论上都应该使用 super 关键字来构造父类
// 在构造函数中,super 代表父类的构造函数
super(name)
// 子类构造函数要使用 this 关键字的话,则必须先调用 super
this.color = color
}
// 重写父类的方法
fly() {
console.log(`${this.name} 在飞`);
// 可以使用super关键字调用父类方法
// 方法中的super代表父类实例
console.log(super.name); // super 只能调用方法不能调用属性
super.fly() // 通过 super 调用父类方法
}
}
let duck = new Duck('卤鸭子', '#f00')
console.log(duck);
duck.fly()
function 类不能使用super
function Phone(_price) {
this.price = _price
}
function IPhone(_price, _color) {
this.price = _price
this.color = _color
}
IPhone.prototype = new Phone()
let iphone = new IPhone(1000, 'pink')
console.log(iphone);
原型链
-
对象中的__proto__,如果存在上级,那么当前对象的__proto__和原型中的__proto__ 形成的一种链式结构就是原型链
-
Object 类型是所有类型的父类
原型链的运行顺序
当访问run函数时,浏览器会先从f对象中寻找run函数,若不存在,就会在父类中寻找,若父类中找不到,就会到父类的父类中寻找直到找到为止
严格模式
w3school 文章: JavaScript “use strict”
为什么要使用严格模式
为了代码更规范,避免一些错误与 js
历史问题所产生的歧义
严格模式中不准做的事情
- 未声明变量就直接赋值
- x = 12
- 对象也一样 x = {a: 1, b: 2}
- 直接删除变量
- delete x
- 重复定义参数名
- function fn(a, a) {}
- 不允许使用 8 进制数
- let x = 010
- 不允许使用八进制转义字符
- let x = '\010'
- 不允许赋值只读属性
- ```js
“use strict”;
const obj = {};
Object.defineProperty(obj, “x”, { value: 0, writable: false });
obj.x = 3.14;
```
- ```js
“use strict”;
const obj = {};
const obj = {
get x() {
return 0;
},
};
obj.x = 3.14;
```
- 删除不允许删除的属性
- delete Object.prototype
eval
arguments
不能重复定义变量
- let eval = 3.14
- 不能使用
with
语法
- with (Math){x = cos(2)};
- 不能用
eval()
函数创建变量
- eval ("let x = 2")
- *
this
关键字在函数内,默认为undefined
- ```js
function fn(){
console.log(this) // => undefined
}
```
开启严格模式的方法
在 script 标签或 js 文件的顶部,在函数内的第一行,均可以输入 'use strict'
来开启严格模式
Object类方法和访问器
object的常用静态方法
Object.assign :扩展对象
作用:将一个对象的属性复制到另一个对象中,且返回一个新对象
第一个参数:要扩展属性的对象
第二个参数:扩展的属性源对象
Object.assign(x, y) 这句话的含义就是,将y中的属性拷贝到x中,且返回x对象
Object.assign 该函数常用于浅拷贝对象
// 浅拷贝例子
let user = {
name: '金牌猎人'
}
obj = {
isOk: true,
msg: 'hello',
num: 123,
user: user // 引用地址
}
r = Object.assign({}, obj)
// r.user 引用地址 拷贝自 obj.user
console.log(r === obj);
// 修改拷贝后的数据
// r.user.name = '亡命之徒'
// 原始数据 obj 也被修改了
console.log(obj.user.name);
// 结论: 浅拷贝的结果,引用地址会被直接复制
深度拷贝:数据中所有的对象将被复制,而不是复制引用地址
使用序列化和反序列化实现深拷贝
// 深度拷贝: 数据中所有的对象将被复制,而不是复制引用地址
// 使用序列化和反序列化实现深拷贝
r = JSON.parse(JSON.stringify(obj))
console.log(r === obj);
r.user.name = '亡命之徒'
console.log(obj.user.name);
console.log(r.user.name);
给对象定义属性Object.defineProperty
参考:
Object.defineProperty() - JavaScript | MDN
obj = {}
obj.x = 1
obj['y'] = 2
console.log(obj)
// 通过 Object.defineProperty 定义属性
// 第一个参数:要定义属性的对象
// 第二个参数:要定义的属性名称
// 第三个参数:关于指定属性的配置,是一个json对象
Object.defineProperty(obj, 'z', {
// 属性值
// value: 3,
// 是否可写
// writable: true,
// 书写访问器时 不允许添加 value 和 writable 属性
get() {
// 访问中通过this访问当前对象
return this.x
},
set(value) {
this.x = value
}
})
console.log(obj)
Object对象上的toString方法
所有类型都是Object类型的子类
toString 方法 将用在转换字符串时
所有隐式转换对象为字符串的情况下,js会自动调用对象的 toString 方法
// Object 对象上的 toString 方法
// 所有类型都是Object类型的子类
// toString 方法 将用在转换字符串时
// 所有隐式转换对象为字符串的情况下,js会自动调用对象的 toString 方法
console.log(new Number(123).toString());
function Car() {
this.name = '车'
}
console.log(new Car().toString());
// 重写 toString
function Bird() {
this.name = '鸟'
// 重写 toString
this.toString = function () {
return JSON.stringify(this)
}
}
let b = new Bird()
console.log(String(b));
console.log(b + '123');
delete 删除对象属性,对象行为 等价于Reflect.deleteProperty
// delete 删除 对象属性,对象行为
let obj = {
x: 1,
y: 2,
sayGreet: () => {
console.log('greeting')
}
}
// delete 语法:
// delete object.attrName
// 删除属性如下
delete obj.y
delete obj.sayGreet
console.log(obj);
// delete 关键字的功能 等价于 Reflect.deleteProperty
Reflect.deleteProperty(obj, 'x')
console.log(obj);
包装类
将我们熟知的基本的数据类型用类来进行包装,这种类就是包装类
作用
多用于类型转换
包装类有以下几种
Boolean
Number
String
Object
Array
Function
BigInt
Symbol
包装类还包含一些可以调用的函数
// 包装类还包含一些可以调用的函数
// 例如 用 Array.isArray 来判断一个变量是否是数组
let arr = []
console.log(Array.isArray(arr)) // ---> true
console.log(Array.from(new Set(arr)));
// 将变量转换为整数
console.log(Number.parseInt(0.5)) // ---> 0
// 将变量转化为浮点数(小数)
console.log(Number.parseFloat('2.9')) // ---> 2.9
// 保留数字小数点后指定位数
console.log(Number(0).toFixed(2))
// Number.isNaN
// Number.isFinite
// 包装类在参与运算的时候会被自动解包成对应的数据类型
console.log(typeof String('hello world'))
console.log(typeof Number(123))
访问器getter和setter
访问器
访问属性用的函数,访问有两重含义,读值和赋值
作用:
可以控制读取属性的结果,可以控制赋值属性的内容(控制访问)
具体作用
getter:格式化数据显示。简化路径访问
setter:验证赋值参数的合法性
声明访问器
在对象中声明访问器
// 对象中声明访问器
let obj = {
_name: '张三',
// 读值访问器 称为 getter
// get 关键字 后面跟上一个访问器的名称
get name() {
// getter 访问器需要返回一个值
return `姓名: ${this._name}`
},
// 赋值访问器 称为 setter
// set 关键字 后面跟上访问器名称
set name(value) {
// value: 赋值属性时的参数
this._name = value === '张三' ? '法外狂徒' : value
}
// getter 和 setter 是一对,所以名称可以相同
}
// 使用访问器
// 访问器被当作属性使用
console.log(obj.name);
obj.name = '李四'
在类中声明访问器
// 在类中声明访问器
class Human {
#sex
constructor(sex) {
this.#sex = sex
}
// 声明访问器
get sex() {
return this.#sex === 'male' ? '男' :
this.#sex === 'female' ? '女' : '其他'
}
set sex(value) {
this.#sex = value === '男' ? 'male' :
value === '女' ? 'female' : 'other'
}
}
let h = new Human('female')
console.log(h.sex);
h.sex = '其他'
ES6语法
…扩展
…iterable 扩展运算符只能用于可迭代的对象!
复制对象
let obj = { x: 1, y: 2 }
// 复制对象
console.log({ z: 3, ...obj });
复制数组
let arr = [1, 2, 3]
// 复制数组
console.log([...arr, 4]);
快速合并两个数组
// let arr1 = ['a','b']
// let arr2 = [1,2,3]
// let arr3 = [...arr1,...arr2]
// console.log(arr3)
扩展字符串
let str = 'helloworld'
console.log(...str)
var let const区别
-
1.var所声明的变量具备变量提升,let,const所声明的变量也有类似效果但是不属于变量提升,应该是‘暂时性死区’
- 变量提升将所有var所修饰的变量隐式的提前到当前作用域的顶端
-
2.var可以重复声明同名变量,let和const不允许在同一作用域下重复声明同名变量
-
3.var和let在声明变量时可以不用对变量进行初始化,const声明必须初始化
-
4.var不具备块级作用域 ,let和const具备块级作用域 { let }
解构赋值
对数据类型进行解构,然后进行赋值操作
-
对数组进行解构赋值:
var arr = [1,2,3] var [x,y,z] = arr
-
对 对象进行结构赋值
// var obj = { // name: '张三', // age: 18, // } // var { age:y,name:x } = obj // var {age:x,name:y} = obj
rest参数
arguments对象,函数的参数列表
总结:arguments对象是一种类似于数组对象的一种数据结构!但它不是真正的数组类型!可以看作是一个类数组对象!
// 类数组的特点:
// 1.可以使用下标值进行元素值的访问
// 2.可以查看改对象的长度通过 length属性
arguments对象是存在于函数内部中!
arguments的作用是收集函数被调用时所传入的所有实参数据!以类数组的形式进行存储!
rest参数,用于函数的形参列表 …变量名
function fun(a,b,c,...args){
console.log(a,b,c)
console.log(args)
console.log(Array.isArray(args)) // true 真数组
// rest参数作用:
// 1.想替代传统的arguments对象
// 2.rest参数是收集多余的参数值,以数组的形式进行存储
// 3.rest参数只能放在形参列表的最后
}
fun(100,200,300,400,500)
Map和Set
Map的使用类似 json 对象
声明map
let map = new Map([
['a', true],
['b', 123],
['c', 'string']
])
读值 map.get()
赋值 map.set()
迭代map
// 迭代map
let keys = map.keys()
console.log(keys);
let key
// keys.next() 方法的作用是迭代下一个成员 返回一个对象
// 返回对象包括 value 属性和 done 属性 done 代表是否迭代完成
while (!(key = keys.next()).done) {
// value 就是每一个key
console.log(key.value);
console.log(map.get(key.value));
}
// 可以使用 forEach 迭代
map.forEach((value, key, _map) => {
// value map对应key 的值
// key 迭代的键
console.log(value);
console.log(key);
// _map 当前正在迭代的map
console.log(_map);
})
// for of 遍历 Map
for (const entry of map) {
// entry 是包含了一组 key value 的数组
console.log(entry[0]); // => 0 号成员就是key
console.log(entry[1]); // => 1 号成员就是value
}
has 判断是否存在某个key值
set 不重复的数据集,若存入多个重复数据会自动去重
添加 set.add()
删除 set.delete()
判断是否存在某个值 has
迭代set
// for of 遍历 Set
for (const value of set) {
console.log(value);
}
keys = set.keys()
while (!(key = keys.next()).done) {
console.log(key.value);
}
// foreach 迭代 set
set.forEach((value1, value2, _set) => {
console.log(value1);
console.log(value2);
console.log(_set);
})
- 给数组去重
// 通过 set 转换成数组
// 可以通过这种方法来让数组去重
let arr = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
console.log(Array.from(new Set(arr)));
map和set的size属性代表数据长度
注意:但凡包含迭代器的对象都可以使用 forof 来迭代