目录
1.Javascript简介与基础语法:
变量:
<script>
// 变量
// 变量就是存储数据的容器
// 变量是一个可以变化的量,数据可以发生变化
// js 中预留了一些代表特殊含义的英文单词,这些单词我们称为关键字
// 声明变量
// 语法:var 变量名
var a; // 声明后不赋初始值 默认都为 undefined
// 声明变量并赋予初始值
var b = 5;
// 同时声明多个变量并赋予初始值
var c, d = 10, e;
// 修改变量
// 变量在存储新值之前讲完全弃用原来的值
c = 2
// 还可以使用 let 来声明变量
// let 声明变量和使用变量的方法和 var 没有区别
let x, y, z = 88
// let 变量不能重复定义 var 变量可以重复定义
// 常量
// 恒定不变的值为常量
// 声明时必须赋初始值 且声明后无法修改
const g = 10
const pi = 3.1415926
// 访问变量或常量
// 变量值存进容器以后,我们需要从容器中取出来进行使用,这个过程就叫访问
console.log(b);
console.log(g);
console.log(pi);
// 声明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);
// 变量的命名规范:
// 1,必须以字母开头,或者使用 $ 或者 _ 开头 ,不能用数字,变量名建议不要使用中文
// 2,小驼峰命名法: 多个单词构成的名字,第一个单词全小写,后面的首字母大写
// 3,为了提高代码的可读性,命名规范,见名知意
// 4,不能使用关键字(有特殊功能的名字:class、var、this)
let _a // 下划线开头的变量一般是局部变量出现在函数中作为形式参数
let $body // $ 开头一般是jquery对象
let bird // 用字母开头
let blueBird // 多个单词,首个单词的首字母小写,其余单词的首字母大写
let simpleDateFormat
// let const // 由于const是关键字,所以不能用作变量名
</script>
js代码的执行顺序:
<!-- 若有多个script,那么从上而下依序执行 -->
<script>
console.log('hello world')
</script>
<script>
console.log('next script')
// 在同一个script标签中,js的执行顺序
// 以一句话为单位,基本顺序:从上到下,从左到右,每句话用分号隔开“;”
console.log('this is a'); console.log('this is b'); console.log('this is c');
console.log('this is d'); console.log('this is e'); console.log('this is f');
// 若代码中每一行只有一句js,那么句尾的分号可以省略
console.log('这句话没有打分号')
// 赋值运算的顺序: 先执行赋值符右侧代码,再赋值给左侧
let a = 3 + 4 * 2
console.log('a: ', a);
// 函数调用顺序
let fn1 = (x, y, z) => {
console.log(x)
console.log(y)
console.log(z)
return x + y + z
}
let fn2 = () => {
return 1
}
let fn3 = () => {
return 2
}
// debugger 的作用:可以给代码加断点(breakpoint),程序运行到断点处,会停下来,开发人员可以查看此时变量的值,且可以手动一步一步运行程序进行调试
console.log('debugger前');
debugger
console.log('debugger后');
// 以下代码执行顺序:
// 从左到右执行,若存在函数,先执行函数
// 若在执行的函数中存在其他函数,例如下面的fn1的调用,那么会先执行内部的函数,如 fn3 fn2,最后执行 fn1
c = fn2() + 4 + fn1(fn3(), fn2(), 11)
</script>
第一个js代码:
<!-- 使用 script 标签引入外部js文件
src 是文件路径
-->
<script src="./main.js"></script>
<!-- script 标签写在body后面 -->
<script>
// console.log 打印日志到控制台
console.log('hello world')
</script>
2.数据类型:
BigInt:
<script>
// BigInt 长整型
// 用来描述一个位数非常长的整型数字
let a = 1n;
console.log(a);
console.log(typeof a); // => bigint
// bigint 类型的数只能和另一个 bigint 类型的数进行运算
// 否则会抛出异常
console.log(124n / 2n);
</script>
Boolean:
<script>
// boolean 布尔型数据
// 该类型数据用于描述对错真假,只有两个值 true(真) false(假)
let a = true
// 判断数据类型
console.log(typeof a); // => boolean
</script>
Null:
<script>
// null 空类型
// 空类型唯一的值就是 null
// null的含义:用来代表对象类型的空引用(空指针)
let a = null
console.log(a); // => null
// 由于 null 代表的是 引用地址(指针) 又因为 js 把引用地址理解为对象,所以此处输出 object
console.log(typeof a); // => object
</script>
Object:
<script>
// Object 类型
// 对象是一组数据的集合
// 所有能被构造的(包含constructor构造器)类型都是对象
// 所以 函数 数组 本质上也是对象
// 朴素对象 也叫做 json 对象
let obj = {
name: '张三',
sex: 'male',
age: 17
}
// 什么是 json?
// json 是 js 的轻量化数据结构
// json 文件特点
// 1.json 文件中,根节点只能有一组 {} 花括号或 一组 [] 方括号
// 2.属性 key 必须由双引号包裹 value 若是字符串,则必须是双引号
// 3.每个属性由逗号隔开,最后一个属性后面的逗号必须省略
// 那么如何声明一个 json 对象呢?
// 1.直接使用json格式
let user = {
"name": "张三",
"sex": "男",
"age": 17
}
// 2.key 不包含空白符和其他特殊符号时 可以不加引号
user = {
// 字符串可以不用双引号
name: '张三',
sex: 'male',
age: 17
}
let name = '法外狂徒男枪'
let sex = 'other'
let age = 17
// 3.变量名若和对象的属性名相同,则可以省略 如下:
user = {
// 完整写法
// name: name,
name,
sex,
age
}
console.log(user);
// 数组和函数都属于对象类型
let arr = [1, 2, 3]
function fn() { }
console.log(typeof arr);
console.log(typeof fn);// => function 虽然 typeof 检测函数结果为 functiong 但由于函数包含constucktion
// 学习面向对象的时候再介绍类实例
</script>
String:
<script>
// String 字符串类型
// 将很多的字符串起来就是字符串
// 可以使用单双引号来定义字符串
let str = 'hello world'
console.log(typeof str);
str = ''// 空字符串
console.log(typeof str);
str = "hello world"
console.log(typeof str);
// 什么叫转义?
// 转变字符原有的含义就成为转义
// 转义后的字符就是转义字符
// 常见转义字符:\n 换行 \t 制表符
str = '你好\n世界'
console.log(str);
str = 'hello\tworld'
console.log(str);
// 单引号的字符串显示单引号的方法
str = 'ac\'dc'
console.log(str);
// 双引号的字符串显示双引号的方法
str = "ac\"dc"
console.log(str);
// 通常可以在双引号中直接书写单引号
// 单引号的字符串中直接书写双引号
str = 'abc"d'
console.log(str);
// 若两个值通过加法运算符连接,任意一个值为字符串类型,则加法运算符会直接进行拼接字符串
let a = 'hello',b = 123
console.log(a + b);
</script>
Symbol:
<script>
// mdn数据类型:
// 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 { }
let a = new A
for (const key in a){}
</script>
Undefined:
<script>
// undefined 未定义类型
// 一个变量声明后不赋值,则它的值为 undefined
// 未定义类型中只有一个值叫做 undefined
// let a = undefined
let a
console.log(typeof a);// => undefined
a = 123
console.log(typeof a);
// 赋值 undefined
a = undefined
console.log(a);
console.log(typeof a); // => undefined
</script>
Number:
<script>
// 数字类型用于描述数字
let a = 234
// 代表无限大的数字,是数字类型的值
a = Infinity
// 代表不是数字,是数字类型的值 NaN(Not a Number)
a = NaN
console.log('abc' / 123);// 运算结果不是数字 所以显示 NaN
// 有时需要判断一个值是否是NaN
// 可以使用以下方法
// 如果是NaN则返回true,否则返回false
console.log(isNaN(a));
// 浮点数
a = 0.618
console.log(typeof a);
// 科学计数法
a = 3e3
a = 3e-3
console.log(a);
console.log(typeof a);
// 单双精度浮点数是什么意思
// 单精度浮点数就是32位的小数
// 双精度浮点数就是64位的小数
</script>
序列化和反序列化:
<script>
// 序列化和反序列化是什么?
// 序列化就是将对象转换成字符串
// 反序列化就是将字符串转换成对象
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);
</script>
数据类型的转换:
<script>
// 不同类型之间的数据是可以互相转换类型的,这个过程叫数据转换
// 数据转换的方法分为两种:显式转换,隐式转换
// 显式转换:在代码中有明确的代码用于转换类型
// 隐式转换:在代码中没有明确的代码用于转换类型
// Boolean转换成其他类型
let b = true
// 转换数字
b = Number(b)
// 除了 Number 以外,还能使用 parseInt parseFloat 进行转换
console.log(b);// => 1
b = false
b = Number(b)
console.log(b);// => 0
// true 转换数字为 1,false 转换数字为 0
// 转换成字符串
b = true
b = String(b)
console.log(b);// => true 的字符串
b = false
b = b + ''// 连接字符串时,js 会自动转换数据格式为字符串,这是一个隐式转换
console.log(b);// => false 的字符串
// 数字类型转其他类型
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(num)
console.log(num);// => 1314
console.log(typeof num);// => String
// 字符串转其他类型
let str = 'hello world'
// 转 boolean 值
str = Boolean(str)
console.log(str);// => true
str = ''
str = Boolean(str)
console.log(str);// => false
// 转空字符串为 false 其余为 true
// 转数字
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);// => 600
// 若字符串代表的是合法数字则转换为对应数字 否则为NaN
// 对象转其他类型
let obj = { a: 1, b: 2 }
// 转 boolean 值
obj = Boolean(obj)
console.log(obj);// => true
obj = null // 此处赋值 undefined 效果一样
obj = Boolean(obj)
console.log(obj);// => false
// 变量是 null 或 undefined 时 转换结果为false
// 对象只要存在 结果就为 true
// 转数字
obj = { a: 1, b: 2 }
obj = Number(obj)
console.log(obj);// => NaN
// 转字符串
obj = { a: 1, b: 2 }
obj = String(obj)
console.log(obj);// => [object Object]
// 对象强转字符串结果始终为 [object Object]
// 若希望将对象转换成字符串后能够查看其数据,则可以使用序列化
</script>
Reflect:
<script>
// Reflect 反射
// 反射是用来操作对象的方法
// 文档:
let obj = { a: 1, b: 2 }
// obj.c = 3
// obj['e1_!'] = 4
// 定义对象属性
// 作用和 Object.defineProperty 相同
// Object.defineProperty(obj, 'c', {
// // 属性值
// value: 3,
// // 是否可写
// writable: false
// })
// 第一个参数:要定义属性的对象
// 第二个参数:要定义的属性名称
// 第三个参数:配置对象
// 默认情况下用此方法添加的属性值是不可修改的
Reflect.defineProperty(obj, 'c', {
value: 5,
writable: true
})
// 删除属性
// 等价于 delete obj['b']
// delete obj.a
// delete obj['b']
// 第一个参数:要删除属性的对象
// 第二个参数:要删除的属性名称
Reflect.deleteProperty(obj, 'a')
// 返回对象的所有 key 数组
let r = Reflect.ownKeys(obj)
console.log(r);
// 判断对象是否存在某属性
// 第一个参数:要判断属性是否存在的对象
// 第二个参数:要判断是否存在的属性名称
r = Reflect.has(obj, 'e')
console.log(r);
r = Reflect.has(obj, 'c')
console.log(r);
// 判断一个对象是否存在某属性的其他方法
// 1. 使用if的隐式转换
if (obj.e) {
// obj.e 若等于以下值:null undefined '' 0 ==> false 其余情况为true
// if 语句将隐式转换数据为 boolean 值
// 存在e
console.log('e存在');
}
// 2. 使用typeof
console.log(typeof obj.e);
</script>
3.节点操作:
插入节点:
<script>
// 插入节点分两个步骤
// 1.创建节点
// 2.插入节点到文档中
// 创建节点
// 参数是标签名
// 返回一个dom对象
let img = document.createElement('img')
// 设置图片源
img.src = '../img/可可.JPG'
// 插入节点
// document.body 就是body标签的dom对象
// appendChild 追加一个子元素
// 参数是要追加的元素
document.body.appendChild(img)
let ok = document.querySelector('.ok')
// insertBefore 插入节点到另一个节点前
// 第一个参数:要插入的元素
// 第二个参数:插入位置的子节点
document.body.insertBefore(img,ok)
// 创建元素并插入元素的用途
// 1.异步加载 js css 等文件
// 例如异步加载js
let loadJsBtn = document.querySelector('.load-js')
loadJsBtn.addEventListener('click', () => {
// 异步加载js
// 创建标签
let script = document.createElement('script')
script.src = './js/main.js'
// 插入script标签到body中,这样js脚本将自动加载
document.body.appendChild(script)
})
// 2.动态追加一些以html作为模板的元素
</script>
查询节点:
<body>
<table border>
<tbody>
<tr>
<td>
1-1
</td>
<td>1-2</td>
<td>1-3</td>
</tr>
<tr>
<td>2-1</td>
<td>2-2</td>
<td>2-3</td>
</tr>
<tr class="tr">
<td>3-1</td>
<td>3-2</td>
<td>3-3</td>
</tr>
<tr>
<td>4-1</td>
<td>4-2</td>
<td>4-3</td>
</tr>
</tbody>
</table>
</body>
<script>
// 查询节点
// dom对象中可以查询其父节点和子节点
// 获取tr
let tr = document.querySelector('.tr')
// 访问子节点使用 children 属性
console.log(tr.children)
// children 属性包含一个子节点的集合
// 所以访问具体子节点可以使用索引值
let node = tr.children[1]
node.remove()
// 访问父节点使用 parentElement 属性
// 例如 访问 tr 节点树 tbody 如下:
console.log(tr.parentElement);
console.log(tr.parentElement.parentElement);
</script>
删除节点:
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<button>下载</button>
</body>
<script>
let li = document.querySelector('ul>li:nth-of-type(3)')
let ul = document.querySelector('ul')
let btn = document.querySelector('button')
// 节点的remove可以删除元素
// li.remove()
// removeChild 删除子节点
// 参数是想要删除的子节点dom对象
ul.removeChild(li)
// 用途:
// 动态加载一次性js脚本资源,加载完后就可以删除元素了
// 例如:下载按钮
btn.addEventListener('click', () => {
// 动态创建a标签并点击它 来实现下载
let a = document.createElement('a')
// download 可以指定下载后的文件名
a.download = '1.png'
// 下载文件的地址
a.href = './img/可可.jpg'
// 无需插入页面即可点击
a.click()
// 若a标签以插入页面 则需要调用 remove 来删除
a.remove()
})
</script>
替换节点:
<body>
<table border>
<tr>
<td>1-1</td>
<td>1-2</td>
<td>1-3</td>
</tr>
<tr class="tr">
<td>2-1</td>
<td>2-2</td>
<td>2-3</td>
</tr>
<tr>
<td>4-1</td>
<td>4-2</td>
<td>4-3</td>
</tr>
</table>
</body>
<script>
let tr = document.querySelector('.tr')
let _td = document.querySelector('.tr>td:nth-of-type(2)')
// 替换节点语法:
// parent.replaceChild(newNode,child)
// parent:要替换节点的父节点
// newNode:新的要插入文档的节点
// child:被替换的节点
// 构造一个新的元素用于替换
let td = document.createElement('td')
td.textContent = 'hello world'
tr.replaceChild(td, _td)
</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>
通过class来设置样式:
<style>
.box {
width: 100px;
height: 100px;
background-color: #000;
}
.box.active {
background: #ff0;
}
</style>
<body>
<div class="box"></div>
<button>开灯</button>
</body>
<script>
// 查询元素
let btn = document.querySelector('button')
let box = document.querySelector('.box')
btn.addEventListener('click', () => {
// 判断 box 中是否包含 active 类名
// contains 返回 true 代表包含 否则不包含
let r = box.classList.contains('active')
if (r) {
// 包含 active 的话
// 删除 active存在
box.classList.remove('active')
}
else {
// 若不包含 active
// 则添加类名
box.classList.add('active')
}
})
</script>
自定义属性:
<style>
.btn {
width: 100px;
height: 100px;
background-color: #f00;
}
</style>
<body>
<div class="btn">设置属性</div>
<div class="read-btn">读取属性</div>
</body>
<script>
let btn = document.querySelector('.btn')
let readbtn = document.querySelector('.read-btn')
btn.addEventListener('click', () => {
// 什么是自定义的html属性?
// 自定义的 费浏览器故犯的一些 html 属性 就是自定义属性
// 设置自定义属性
// 第一个参数:属性名
// 第二个参数:属性值,请填入字符串类型的值,否则将被强转成字符串
// btn.setAttribute(key,value)
// 请不要直接填对象
// btn.setAttribute('my-attr', { a: 1, b: 2 })
btn.setAttribute('my-attr', '你好')
// btn.setAttribute('id', 'ok')
// 若要设置对象就应该序列化
btn.setAttribute('my-obj', JSON.stringify({ a: 1, b: 2 }))
})
readbtn.addEventListener('click', () => {
// 读取属性
let r = btn.getAttribute('my-attr')
console.log(r);
r = btn.getAttribute('my-obj')
console.log(JSON.parse(r));
})
// 应用场景
// 当你希望将参数保存到html标签上时,可以考虑自定义属性
</script>
child:
<body>
<h1>hello child</h1>
<div>
<input type="text">
<button>发送</button>
</div>
</body>
<script>
let input = document.querySelector('input')
let button = document.querySelector('button')
// 接收消息
window.addEventListener('message', ev => {
console.log(ev);
// 读取消息参数
console.log(ev.data);
})
button.addEventListener('click', () => {
// 子窗口向父窗口发送消息
// window.parent 当前窗口的父窗口
window.parent.postMessage(input.value)
})
</script>
window:
<body>
<!-- <iframe name="child" src="https://www.bilibili.com/" style="width: 500px;height:300;"></iframe> -->
<iframe name="child" src="./child.html" style="width: 500px;height:300px;"></iframe>
<div>
消息:<input type="text" name="msg">
<button class="send1">提交</button>
</div>
</body>
<script>
// 文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Window#%E6%96%B9%E6%B3%95
// window对象的相关属性和方法,在调用的时候可以省略window
// 例如 window.open() 可以写作 open()
//打开窗口
// 第一个参数:网址
// 第二个参数:iframe名称,没有的话就打开新窗口
// windou.open('https://www.baidu.com/','child')
// 关闭窗口
// window.close()
// 窗口间通信
// 原理:
// 通过窗口对象(window)发送消息(postMessage)
// 另一个窗口需要通过 message 事件来接收消息
// 例子:父窗口向子窗口发消息
let msgInp = document.querySelector('input[name=msg]')
let send1 = document.querySelector('.send1')
let iframe = document.querySelector('iframe[name=child]')
send1.addEventListener('click', () => {
// 发送消息
// 向目标窗口发送消息
// iframe.contentWindow 代表 iframe 中的窗口对象
iframe.contentWindow.postMessage(msgInp.value)
})
// 接受子窗口的消息
window.addEventListener('message', ev => {
console.log(ev.data);
})
</script>
location:
<body>
<div>
网址:<input type="text" name="msg">
<button>跳转</button>
</div>
</body>
<script>
// location 代表浏览器地址栏
// 跳转网页
let input = document.querySelector('input')
let btn = document.querySelector('button')
btn.addEventListener('click', () => {
// 给 location.href 赋值页面就会跳转
window.location.href = input.value
})
// 跳转网页不计入历史
// window.location.replace('https://www.bilibili.com/')
// 刷新页面
// window.location.reload()
// 地址栏参数
// location.search
</script>
history:
<script>
// history 浏览器浏览记录(历史记录)
// 文档:https://developer.mozilla.org/zh-CN/docs/Web/API/History
// 前进
// window.history.forward()
// 后退
// window.history.back()
// 转到指定位置
// history.go(delta)
// history.go(1) 等价于 history.forward()
// history.go(-1) 等价于 history.back()
</script>
localStorage:
<script>
// localStorage 数据持久化到浏览器中,关闭窗口或浏览器,都不会消失
// 设置数据
// localStorage.setItem('a', JSON.stringify({ x: 1, y: 2 }))
// localStorage 只能存字符串
// localStorage['b'] = JSON.stringify([1, 2, 3])
// 等价于调用 setItem 方法
// 读取数据
console.log(localStorage.getItem('a'));
console.log(localStorage['b']);// 等价于调用 getItem 方法
// sessionStorage 用法和 localStorage 相同,但是窗口关闭数据就消失了
sessionStorage['a'] = 1
sessionStorage['b'] = 2
</script>
navigator:
<script>
// navigator 用于查看设备信息
// 文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator
console.log(navigator);
</script>
4.运算符和条件判断
比较运算符:
<body>
比较运算符: >,<,>=,<=,==,!=,===,!==
©
< >
<!-- & 符号开头 ; 分号结尾的内容 我们成为 html 实体 -->
</body>
<script>
// 比较运算符用于对运算符左右两个值进行比较
// 比较运算符的运算结果为boolean值,用于指示比较结果为真或者假
// 大于
console.log(1 > 2);// => false
// 小于
console.log(1 < 2);// => true
// 大于等于 小于等于
console.log(3 >= 3);
console.log(3 <= 2);
// == 相等判断 (不建议使用)
// 非严格相等判断 在运算符左右两边,不需要使用相同的数据类型
// 当数据类型不同是时 ==运算符将自动进行隐式转换
// 参考:
// 非严格相等判断,可以不同类型的数据拿来进行比较,所以有时可能会得到期望以外的结果,不推荐使用
let a = '', b = 0
// 当运算符两边的数据类型不同时,自动将其转成相同类型
console.log(a == b);
// === 严格相等
// 类型和值都相等才会被认为相等
let x = ''
let y = 0
console.log(x == y);
console.log(x === y);
// 对象相等判断
let obj1 = { name: '张三' }
let obj2 = { name: '张三' }
// 由于对象类型的变量保存的对象的引用地址
// 所以此处两对象引用地址不同 结果为false
console.log(obj1 === obj2);
// != 非严格不等于
console.log('' != 0);
// !== 严格不等于
console.log('' !== 0);
</script>
赋值运算符:
<body>
赋值运算符 := += -= *= /= %= ++ --
</body>
<script>
// 赋值运算符
let a = 1
// += 自增
a += 2 //等价于 a = a + 2
console.log(a);
// -= 自减
a -= 5 //等价于 a = a - 5
console.log(a);
// *= 自乘
a *= -1 //等价于 a = a * -1
console.log(a);
// /= 自除
a /= 2 //等价于 a = a / 2
console.log(a);
// %= 自模
a = 5
a %= 3 //等价于 a = a % 3
console.log(a);
// ++ 自增
a = 0
a++
console.log(a);
// --自减
a = 0
a--
console.log(a);
a = 0
// 自增自减运算符位置的区别
// 运算符放前面,则先运算后引用
console.log(--a);
// 运算符放后面,则先引用后运算
a = 0
console.log(a++);
a = 0
console.log(1 + ++a);// => 2
console.log(3 - a-- / 2);// => 2.5
console.log(a);// => 0
</script>
逻辑运算符:
<body>
逻辑运算符:&&与 ||或 !非 ~异或 &按位与 |按位或
</body>
<script>
// 逻辑运算符:符合逻辑条件时返回真或假
// bool expression 的意思是:布尔表达式
// 什么是布尔表达式?整个js代码运算结果是一个布尔值,这样的代码片段叫做布尔表达式
// 举例如下:
console.log(1 === '');
console.log(true);
let user = {
sex: 'other',
age: 24
}
// && 与
// 语法: bool expression && bool expression
// 两个表达式都为真,结果返回真,任意一个表达式为假,结果返回为假
// 例如:
console.log(true && true);
console.log(false && true);
console.log(true && false);
console.log(user.sex === 'other' && user.age > 20);
// || 或
// 语法:bool expression || other expression
// 两个表达式任意一个为真,结果返回真,否则为假
console.log(true || true);
console.log(false || user);
console.log(true || false);
console.log(false || false);
// && 和 || 开发人员称为短路运算
// 短路运算的应用场景1: 赋值默认值
let a// => undefined
let b = a || 5
console.log(b);
// 短路运算的应用场景2:赋值默认值
let x = user.age > 20
// x && console.log((user.age>20))
x && console.log('user.age 大于 20')
user.age > 20 && console.log('user.age 大于 20');
// !非 作用是取反
// 语法:!(tool express)
console.log(!true);
console.log(!false);
</script>
三元运算符:
<script>
// 三元运算符:用于判断一个表达式结果,为真时返回结果1,为假时返回结果2
// 语法:bool expression? case1: case2
// 作用:主要用在给变量赋值
let obj = {
name: '张三',
sex: 'male',
age: 16
}
console.log(obj.sex === 'male' ? '男' : '女');
obj.sex = 'other'
// 嵌套三元运算符 在'?'或':'处可以换行
console.log(obj.sex === 'male' ? '男' :
obj.sex === 'female' ? '女' : '其他');
// 当嵌套三元运算符在同一行内书写,请给嵌套的三元运算符的表达式用圆括号包起来
// console.log(obj.sex === 'male' ? '男' : obj.sex && 'female' ? '女' : '其他');
console.log(obj.sex === 'male' ? '男' : (obj.sex === 'female' ? '女' : '其他'));
</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);
// 算数运算符遵循数学的符号优先级
console.log(1 + 2 * 3 ** 2);
// 可以使用圆括号来分组
console.log(1 + (2 * 3) ** 2);
</script>
运算优先级顺序:
++和-- > 算术运算符 > 比较运算符 > 逻辑运算符 === 三元运算符 > 赋值运算符
if条件判断:
<script>
let user = {
name: '张三',
sex: 'male',
age: 17,
score: 60
}
// if 语句:用于进行条件判断的语句
// if(bool expression){
// code block 代码块
// }
// 作用:当布尔表达式值为真时,执行后面的代码块
if (user.name === '张三') {
console.log('欢迎');
}
// if else:如果... 否则...
// 语法:
// if(bool expression) {
// code block 代码块
// } else {
// code block 代码块
// }
if (user.score >= 60) {
console.log('及格');
} else {
console.log('不及格');
}
// 三元运算符替代if else
console.log(user.score >= 60 ? '及格' : '不及格');
// if else if else:如果... 或者... 或者... 否则...
// 语法:
// if(bool expression) {
// code block 代码块
// } else if(bool expression) {
// code block 代码块
// } else if(bool expression) {
// code block 代码块
// } else if(bool expression) {
// code block 代码块
// }
// ... 此处 else if 可以写多个
// ...
// else {
// code block
// }
// 最后的 else 是非必填
// if (user.sex === 'male') {
// console.log('男');
// } else if (user.sex === 'female') {
// console.log('女');
// } else {
// console.log('其他');
// }
// if else if else 语句,若代码块中只有一句话 则可以省略花括号
if (user.sex === 'male') console.log('男');
else if (user.sex === 'female')console.log('女');
else console.log('其他');
// 总结:
// 1. if 语句要么执行要么不执行
// 2. if else 语句,一定有一个会被执行,二选一
// 3. if else if 语句,多选一或都不执行
// 4. if else if else 语句,一定有一个会被执行,多选一
</script>
switch条件判断:
<script>
// switch 语句
// 语法:判断value值为多少,如果为constant1则执行其后的代码
// 如果为constant2则执行其后的代码
// 如果不满足所有的case语句,则执行default
// switch(value){
// case constant1:
// code
// break;
// case constant2:
// break;
// ...可以有任意多个case语句
// default: // default 不是必须要写的
// break;
// }
let r = 6
let myCase = 4
console.log(r);
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;
}
</script>
5.数组和循环
多维数组:
<script>
// 什么是多维数组?
// 多维数组:多个维度(rank)组成的数组
let arr = [1, 2, 3]
// 声明二维数组
// 二维数组中每个一维数组的成员都是一个数组
arr = [[1, 2], [3, 4], [5, 6]]
// 读取数组
// 可以将二维数组视为一个x轴和y轴构成的二维平面,所以任意值都可以由x,y坐标来表示
console.log(arr[0][1]);// => 2
console.log(arr[2][0]);// => 5
// 声明多维数组
// 多维数组是数组内嵌套多层数组的数组
arr = [[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]
console.log(arr[0][1][1]);// =>5
console.log(arr[1][1][0]);// => 10
// 访问数组
// 可以理解成三维空间中访问一个三维坐标系
</script>
如何判断一个变量是否是数组:
<script>
let a = [1,2]
let b = 'hello world'
// Array.isArray 方法检测变量是否为数组
// 是数组的话返回 true 否则为 false
console.log(Array.isArray(a));
console.log(Array.isArray(b));
</script>
数组的基础:
<script>
// 什么是数组?
// 存储一组数据的一个容器
// 声明数组
// 使用脚本形式声明数组
let arr = []
// 声明并初始化数组
// 数组中的每一个数据称为 数组成员
arr = [false, 2, 'hello world', { name: '张三' }, null, undefined]
console.log(arr);
// 对象形式的声明方法
arr = new Array()
console.log(arr);
// 声明并初始化
arr = new Array(false, 2, 'hello world', { name: '张三' }, null, undefined)
console.log(arr);
// 访问数组成员
// 使用索引值访问数组中的成员
// 索引:数组给每个成员的编号成为索引,索引值从零开始一次递增1
// 访问数组成员时,若索引超出数组范围,则获取到的结果为 undefined
console.log(arr['0']);
console.log(arr['1']);
// 数组索引可以不使用引号
console.log(arr[2]);
// 给数组成员赋值
// 使用数组变量名加上索引值进行赋值,可以给指定索引位置的成员进行赋值
arr[4] = {name:'李四'}
console.log(arr[4]);
arr[6] = 333
console.log(arr);
arr[9] = 10
console.log(arr);
// 数组长度(指的就是数组成员的个数)
// 若赋值的时候索引不是正整数,该值将被视为数组对象的属性值,不计入数组成员,所以不会影响数组长度
arr[-1] = true
console.log(arr);
console.log(arr.length);
</script>
数组的操作:
<script>
// 数组的操作
let arr = [1, 2, true, { name: 'Amy' }, 2]
// push 追加数据到数组末尾
// 参数:被添加的新数组成员
arr.push('hello world')
console.log(arr);
// pop 从尾部取出一个成员
// 返回值是取出的成员
let r = arr.pop()
console.log(r);
console.log(arr);
// unshift 在头部添加数据
// 参数:被添加的新数组成员
arr.unshift('my best day')
console.log(arr);
// shift 从头部取出一个成员
// 返回值是取出的成员
r = arr.shift()
console.log(r);
console.log(arr);
// push 和 unshift 可以批量添加成员
arr.push('a', 'b', 'c')
arr.unshift('x', 'y', 'z')
console.log(arr);
// splice 删除指定位置的成员,并用新成员替换,或插入新成员到指定成员的前面
// 第一个参数:删除成员的起始位置
// 第二个参数:删除成员的个数
// 第三个参数:用于替换被删除成员的新数据,该参数可省略
// 删除一个成员:
// r = arr.splice(6, 3)
// splice的返回值,就是被删除的成员数组
// console.log(r);
// 在指定成员前追加新数据
// 若第二个参数为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)
console.log(arr2);
console.log(arr === arr2);
// join 使数组成员用一个字符连接起来
// join 函数接收一个参数,该参数就是连接数组成员是使用的字符
arr2 = ['abc', 'xyz', '123']
r = arr2.join('_')
console.log(r);
// includes 判断是否包含某成员
r = arr.includes('z')
console.log(r);
// indexOf 查询指定数组成员的索引
r = arr.indexOf('b')
console.log(r);
// 可以使用indexOf判断是否包含某个数组成员 若不包含 返回 -1
r = arr.indexOf('g')
console.log(r);// => -1
if (arr.indexOf('g') === -1) {
console.log('该数组成员不存在');
}
// lastIndexOf 查询最后一个指定数组成员的索引(因为数组成员可能重复)
console.log(arr.indexOf(2));
console.log(arr.lastIndexOf(2));
// slice 数组切片 获取子数组
// 切片遵循“前截后不截”原理:起始位置包含在结果内,结束位置不包含
console.log(arr.slice(5, 8));
// 参数只有一个,代表从该位置开始一直截取到最后
console.log(arr.slice(5));
</script>
流程控制-循环:
<script>
// 循环是什么?
// 循环就是重复执行某一段代码的行为
// for 循环
// 例如按次数进行循环
let count = 5 //循环次数
// for (let i = 0; i < count; i++) {
// console.log('hello world')
// }
// 语法:
// for (初始化语句; 循环条件; code block 运行一次结束后运行的代码) { // code block }
// 初始化语句:初始化变量值
// 循环条件:循环条件是个布尔表达式,若结果为真,则执行 code block 的代码块
let index = 0
// for (; index < count; index++) {
// console.log('hello world');
// }
// 循环的继续与跳出
// 若打印0~100之间所有的偶数,数字大于51则停下 代码如下
// 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: 'female',
age: 18
}
// key:对象属性名称
// for (let key in obj) {
// console.log(key)
// console.log(obj[key])
// }
// let keys = Reflect.ownKeys(obj)
// console.log(keys);
// for (let i = 0; i < keys.length; i++) {
// const key = keys[i]
// console.log(obj[key]);
// }
// for of:用于遍历数组,且不能获取索引值,只能遍历所有成员
// element:数组中每个成员
// for(let element of arr){
// console.log(element);
// }
// for of 可以用在所有存在迭代器的集合对象上,如: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
</script>
冒泡排序法:
<script>
// 冒泡排序
// 原理:此处以排序出一个从小到大的序列为例
// 令数组长度为 length
// 排序过程将进行 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
}
}
alert(arr)
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
}
}
}
</script>
6.函数和数组的遍历函数
变量的作用域:
<script>
// 什么是变量的作用域(Scope)?作用域指的是作用范围,范围内可以访问变量,超出范围则无法访问
// 为了更好理解作用域,可以结合 debugger 和 浏览器的堆栈信息来查看
// 方法:
// 1.在需要查看变量作用的地方写上 debugger
// 2.浏览器上 ctrl + shift + i 或 F12
// 3.选择 sources
// 4.找到当前代码对应的 Call Stack
// 5.查看当前 Call Stack 下的 Scope
// 作用域一共只有三种
// Global 全局作用域
// Block 块级作用域
// Function 函数作用域
// 全局作用域(Global)
// 具备全局作用域的只有全局变量或常量
// 全局变量指的是作用域为全局(Global)的变量,代码中任何位置都能使用
// 全局变量有两种
// 1.自动全局变量
// 给未定义的变量直接赋值,该变量会变成 自动全局变量,若在浏览器中,该变量会被存到window对象里
zhangSan = { name: '法外狂徒', age: 17 }
// 2.(普通的)全局变量
// 直接在函数外声明的变量也是全局作用域的变量
let luoXiang = { name: '罗老师' }
// 块级作用域(Block)
if (true) {
// 块级作用域就是在代码块中有效的作用域
// let 关键字声明的变量具备块级作用域
// var 关键字声明的变量具备全局作用域
let a = 1
var other = 2
console.log(a);
console.log(other);
}
// for (let i = 0; i < 5; i++) {
// // 经过一段时间后执行里面的回调函数
// setTimeout(() => {
// console.log(i);
// }, 1000)
// }
// 函数作用域(Function)
// 函数中的变量,无论使用var还是let定义的,都是函数作用域,在函数外无法访问
function fn(){
let x = 1
var y = 2
console.log(x);
console.log(y);
}
// console.log(x);
// console.log(y);
// 总结:
// 函数外声明的变量 作用域是全局
// 函数内用关键字声明的变量 作用域是函数内
// let 声明的变量具备块级作用域
</script>
参数的值传递和引用传递:
<script>
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(b);
}
// 传入两个参数
// x 是值类型传参
// y 是引用类型传参
// 值类型传参,将x变量中的值 直接赋予了函数内的形参
// boolean number string 都是值传递的
// 引用类型传参,将y中的引用地址,传递给函数内的形参
// object function 都是引用传递的
modify(x, y)
console.log('函数调用完后');
console.log(x);
console.log(y);
</script>
函数的使用:
<script>
// 函数
// 什么是函数?
// 站在开发人员的角度讲:函数式对一段程序的封装(封装:包装)
// 站在生活的角度来讲,函数就是帮我们完成某样任务的机器
// 为什么要使用函数?
// 用于包装可以重复使用的代码
// 代码的复用
// 输入input 和 输出ouput
// 输入:函数完成任务时必不可少的材料
// 输出:函数执行完后产出的产物
// 定义函数:
// 语法:fuction 函数名 (参数列表){// code block 代码块}
function sayHello() {
console.log('hello');
}
// 调用(call)函数:通过函数名进行调用,并且后面跟上圆括号
sayHello()
// 有参数的函数:
// 参数列表中声明参数,如有多个参数,用逗号隔开
// 参数列表中的参数,我们称为:形式参数
// 返回结果,函数的返回结果称为“返回值”
// return 还会终止函数内后续代码的运行
// 定义一个加法函数
function add(x, y) {
// 做函数的运算
let result = x + y
// return 终止函数运行,并返回一个输出
return result
// 函数若没有返回值(也就是没有return) 那么函数默认返回 undefined
}
// 调用有参函数:通过函数名调用,且调用的同时,传入真实的参数
// 调用函数时,圆括号中的参数叫做:实际参数
let r = add(1, 2)
console.log(r);
// 通过变量(或常量)存储函数和调用函数
// 声明一个sub变量来储存减法函数
// 必须先声明后调用
let sub = function (x, y) {
let result = x - y
return result
}
r = sub(1, 2)
console.log(r);
// sayHow()
// function 定义的函数可以先调用再声明
// function sayHow() {
// console.log('how');
// }
// let sayHow = function () {
// console.log('how');
// }
// 定义参数时,可以给参数设置默认值
function sayMessage(msg = 'hello message') {
console.log(msg);
}
sayMessage('this is my param')
// 当不给函数提供参数时,参数msg将采用默认值
sayMessage()
// 自调用函数
// 定义后立即调用的匿名函数
console.log(((x, y) => x + y)(2, 3));
// 定义调用函数的步骤
// 1. 先打两个圆括号
// 2. 再第一个圆括号中声明函数
// 3. 第二个圆括号代表调用函数,圆括号中填入实际参数
</script>
节流和防抖:
<script>
// 防抖
// 函数将在一个固定时间后被调用,若计时未完成又执行该函数,则取消上次计时,重新开始计时
// 用于限制频繁的网络请求,例如:搜索功能,用户停止输入的一段时间后才会执行搜索任务
// 防抖
// 步骤:
// 1. 取消计时
// 2. 重新计时
const fd = (() => {
// 计时器id
let timerId
return () => {
// 取消计时
clearTimeout(timerId)
// 重新计时
timerId = setTimeout(() => {
// 计时完成后要执行的代码
console.log('hello world');
}, 3000)
}
})()
console.log(fd);
// 封装防抖函数
// 这样任何函数都能被添加防抖功能
function add(x, y) {
console.log('ok');
return x + y
}
// 定义一个函数来封装防抖
// fn:要添加防抖的函数
// delay:防抖延迟多久
// that:防抖函数内的 this 指代
function fdPlus(fn, delay, that) {
let timerId
return () => {
clearTimeout(timerId)
timerId = setTimeout(() => {
// 调用参数 fn 函数
// 将当前函数的参数 arguments 作为 fn 的参数传入进去
fn.apply(that, arguments)
}, delay)
}
}
add = fdPlus(add, 3000)
add(1, 2)
// 节流
// 固定时间内只能调用一次的函数,可以使用时间戳或计时器的方式实现
// 作用同样是限制用户频繁的网络请求,例如:发送验证码
// 时间戳
function jlTimespan() {
// 记录上一次成功运行节流函数的时间
let lastTime
// 函数调用冷却时间
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('hellow 节流');
// 记录本次调用的时间
lastTime = now
}
}
}
}
let fn = jlTimespan()
console.log(fn);
// 计时器
// 1. 判断是否可以运行节流代码
// 2. 执行节流的内容
// 3. 计时
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
}
function jlPlus(fn, delay, that) {
// 计时器id
let timerId
return function () {
if (timerId) return
// 执行节流的代码
fn.apply(that, arguments)
// 计时
timerId = setTimeout(() => {
timerId = undefined
}, delay)
}
}
fn = jlPlus(sub, 5000, 'hello this')
</script>
内存堆栈与垃圾回收:
<script>
// 内存堆栈:
// 调用函数时,函数中的各种变量将存储在内存中,以对战的形式进行保存
// 堆 用于存储对象数据的内存区域
// 栈 指的是一种先进后出的数据格式 用于存储每次函数调用时产生的数据
// 栈中的每一次调用内容,称为一个栈帧
let a = (x, y) => {
let s = 0
return b(x)
}
let b = (x) => {
let num1 = 1
let num2 = 2
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中查看
// 栈将在调用函数时创建数据并进入栈,这个过程就是入栈
// 函数调用结束后将函数中的数据将从栈中移除,这个过程称为出栈
// 出栈时,释放内存中已经无用的数据的过程称为垃圾回收
// 函数调用时栈的存放顺序始终是先进后出
</script>
通过作用域理解var和let的区别:
<script>
// 此处结合 debugger 查看以下 for 循环中 i 的作用域
// for (let i = 0; i < 5; i++) {
// // setTimeout 将在指定的一段时间后 执行回调函数
// setTimeout(() => {
// // debugger
// console.log(i);
// });
// }
for(var i = 0;i<5;i++){
setTimeout(() => {
// debugger
console.log(i);
});
}
</script>
预编译:
<script>
// 参考:https://juejin.cn/post/6844903575571677198
// 预编译是什么?
// 在浏览器执行js脚本前需要进行编译,有些代码在编译前将会率先被翻译执行并存入内存,这个过程称为预编译
// console.log(x + 1)
// 变量无法在声明之前访问它
// let x = 1
console.log(sub(2, 1));
// 声明函数在调用之后
function sub(x, y) {
let a = 1
return x - y
}
// 上述调用函数不会报错,因为使用了 function 关键字声明的函数,将会被预编译
function fn(a) {
console.log(a);
var a = 123;
console.log(a);
function a() { };
console.log(a);
var b = function () { };
console.log(b);
function d() { };
}
console.log('fn 运行开始');
// 调用函数
fn(1)
// 总结:
// 哪些东西会被预编译?
// 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(){}
}
console.log('method 运行结束');
method2(true, function (x, y) {
console.log(x);
console.log(y);
}, 'hello')
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 //因为b()无返回值,所以默认返回值是 undefined
console.log(c);// => hello
var c =123
console.log(d);// => undefined
var d = 789
console.log(c);// => 123
console.log(d);// => 789
}
</script>
arguments变量:
<body>
<button>只调用一次的按钮</button>
<button class="other">递归调用</button>
</body>
<script>
// 什么是 arguments ?
// 用 function 定义的函数中 arguments 代表参数数组,是浏览器隐式声明的变量
// lambda 表达式中不能使用 arguments
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)
let btn = document.querySelector('button')
btn.addEventListener('click', function () {
console.log('click');
// 移除事件监听程序
btn.removeEventListener('click', 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()
})
</script>
bind call apply函数的应用:
<script>
// 函数内可以使用 this 关键字,不同的地方 this 关键字
function fn() {
console.log(this);
}
// fn()
// 函数内的 this 所指代的内容可以通过 bind call apply 来修改
// bind 绑定函数的 this 指代
// 语法 function.bind(thisArg,x,y,...)
// thisArg:要修改的this的指代
// x,y,...:函数的参数,选填
// 返回值:一个被修改了this指代后的新函数
let fn2 = fn.bind('hello world', 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]));
</script>
lambda表达式:
<script>
// lambda(郎木达)表达式
// 什么是 lambda 表达式?是一种用来定义函数的方法
// lambda 表达式也称为箭头函数
// 定义一个无参函数
// 圆括号部分是参数列表
// 花括号部分是代码块
let 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);
</script>
数组的迭代操作:
<script>
let students = [
{ id: 0, name: '张三', sex: 'male', age: 16 },
{ id: 1, name: '李四', sex: 'female', age: 20 },
{ id: 2, name: '隔壁老王', sex: 'other', age: 30 }
]
// 以下所有的遍历函数的参数都是相同的回调函数
// 回调函数接受以下参数
// el 被遍历的当前数组成员
// index 被遍历的当前成员的索引
// arr 被遍历的数组对象
// forEach 循环遍历每一个数组成员
students.forEach((el, index, arr) => {
console.log(el);
console.log(index);
console.log(arr);
})
// 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 { age: el.age }
return el.age
})
console.log(r);
// filter 过滤器
// filter 返回过滤完后的新数组
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 查找
// some 查询是否存在满足条件的成员
// find 查找符合回调函数条件的数组成员并返回它
// 返回值是查找的结果
r = students.find((el, index, arr) => {
// // 查找女同学
// if(el.sex === 'female'){
// // return true 代表当前成员就是要查找的元素
// return true
// }
// return false
return el.name === '隔壁老王'
})
console.log(r);
// findeIndex 查找对应成员所在索引
// 使用方法和 find 相同,返回结果为查找到的成员索引
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', 'c']
r = arr2.some((el, index, arr) => {
// 判断当前数组成员是否在 arr1 中存在
if (arr1.includes(el)) {
// 返回 true 代表 找到一个满足条件的数组成员
return true
}
return false
})
console.log(r);
// sort 排序
// 若调用 sort 函数不传参数时,会使用浏览器默认的排序规则
let numList = [90, 12, 110, 9, 40, 214]
numList.sort()
console.log(numList);
// 自定义排序
// 排序规则:按年龄从小到大排序
// sort 参数是一个排序规则的函数
// 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 的左侧
// 左侧联想 左箭头 <:即小于符号,则此处应该返回一个小于零的值
return -1
} else {
// 若 el1 和 el2 不需要交换顺序 则返回 0
return 0
}
})
console.log(students);
</script>
数组去重:
<script>
// 数组去重
let arr = [1, 5, 1, 2, 2, 4, 6, 5, 6, 4]
// 有以下两种方法供参考
// 1.缓存重复数据
// 缓存
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 去重
temp = {}
result = arr.filter(el => {
// 若缓存中没有el
if(!temp[el]){
// 加入缓存
temp[el] = true
return true
}
return false
})
console.log(result);
// 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)));
</script>
MapReduce统计模型:
<script>
// mapReduce 运算模型是拿来做大数据统计的
// 要统计出结果,要经理 map 和 reduce 两个步骤
// 1. map 的作用 用于返回统计结果所需的数据结构
// 2. reduce 的作用就是具体的统计逻辑
let arr = [
{
name: '李四666',
score: 45,
sex: 'female'
},
{
name: '张三1',
score: 45,
sex: 'female'
},
{
name: '李四1',
score: 89,
sex: 'female'
},
{
name: '老王1',
score: 48,
sex: 'female'
},
{
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. 统计总分和平均分
r = arr.map(el => {
return {
avg: 0, //平均分
sum: 0, //总分
score: el.score, //每个同学的分数
count: 0 //同学的数量
}
})
console.log(r);
// 前置插入一个空对象,用来充当上一次的结果
r.unshift({
avg: 0, //平均分
sum: 0, //总分
score: 0, //每个同学的分数
count: 0 //同学的数量
})
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. 按男女统计分数最低的两个人
r = arr.map(el => {
return {
// 男生分数最低的人
male: [],
// 女生分数最低的人
female: [],
stu: el
}
})
r.unshift({
male: [],
female: [],
stn: 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:17},{score:19}]
// n.stu.score = 18
// 将新数据加入数组
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 ,
stn: null
}
})
console.log(r);
</script>
7.字符串操作
表达式语法:
<script>
// 参考
'abc@qq.com'
let regex = /^\\dcom$/
// \ 斜杠:转义
console.log(/\\abc/.test('\\abc'));
// ^:匹配字符串的开头
regex = /^abc/
console.log(regex.test('abcd'));// => true
console.log(regex.test('abcefg'));// => true
console.log(regex.test('aabcd'));// => false
// $:匹配字符串的结尾
regex = /xyz$/
console.log(regex.test('123xyz'));// => true
console.log(regex.test('666abcxyz'));// => true
console.log(regex.test('666abcxyz.'));// => false
// -----------------匹配字符个数的符号
// 这些匹配字符个数的符号,代表的意思是:匹配前一个字符多少次
// *:匹配任意次
regex = /o*k/
console.log(regex.test('k'));// => true
console.log(regex.test('ok'));// => true
console.log(regex.test('ooooook'));// => true
// ?:匹配0次或1次
regex = /^o?k$/
console.log(regex.test('k'));// => true
console.log(regex.test('ok'));// => true
console.log(regex.test('ooooook'));// => false
// +:匹配至少1次
regex = /^o+k$/
console.log(regex.test('k'));// => false
console.log(regex.test('ok'));// => true
console.log(regex.test('ooooook'));// => true
// {n}:匹配指定次数
regex = /^o{2}k$/
console.log(regex.test('k'));// => false
console.log(regex.test('ok'));// => false
console.log(regex.test('ook'));// => true
// {n,}:匹配至少n次
regex = /^o{2,}k$/
console.log(regex.test('k'));// => false
console.log(regex.test('ok'));// => false
console.log(regex.test('ook'));// => true
console.log(regex.test('oook'));// => true
// {n,m}:匹配至少n次,至多m次
regex = /^o{1,3}k$/
console.log(regex.test('k'));// => false
console.log(regex.test('ok'));// => true
console.log(regex.test('ook'));// => true
console.log(regex.test('oook'));// => true
console.log(regex.test('ooook'));// => false
// ----------------------------匹配字符个数的符号 - end
// [xyz]:匹配字符集合,匹配一个字符,该字符在方括号内
regex = /^[xyz]$/
console.log(regex.test('z'));// => true
console.log(regex.test('yy'));// => false
// x|y:或
regex = /^(good|bad)$/
console.log(regex.test('good')); // => true
console.log(regex.test('bad')); // => true
console.log(regex.test('ok')); // => false
// [^xyz]:匹配负值集合,匹配一个字符,该字符不在方括号内
regex = /^[^xyz]$/
console.log(regex.test('z'));// => false
console.log(regex.test('a'));// => true
console.log(regex.test('abc'));// => false
// [a-z] [0-9]:取范围值,匹配一个字符,该字符在指定范围内
regex = /^[A-Z][0-9]$/
console.log(regex.test('E7'));// => true
console.log(regex.test('G8'));// => true
console.log(regex.test('c4'));// => false
// [^5-7]:取范围负值,匹配一个字符,该字符不在指定范围内
regex = /^[^5-7]$/
console.log(regex.test('6'));// => false
console.log(regex.test('x'));// => true
console.log(regex.test('a'));// => true
// ----------------------------- 分组(pattern)
// (pattern):将pattern里面的所有字符当做一个字符处理
regex = /^abc(123)+xyz$/
console.log(regex.test('abc123xyz'));// => true
console.log(regex.test('abc123123xyz'));// => true
console.log(regex.test('abc112233xyz'));// => false
// 站在字符串的角度看,圆括号不仅有分组的作用,同时,它将被取值
regex = /abc(123)+xyz/
let r = '000123abc123xyz444555'.match(regex)
console.log(r);
// (?:pattern):匹配分组内容,但不获取圆括号中的值
regex = /abc(?:123)+xyz/
r = '000123abc123123xyz444555'.match(regex)
console.log(r);
// assert 断言
// 什么是断言?断言就是一段下断定的言论,例如:今天一定会下雨
// 先行断言
// 语法:x(?=y)
// x 跟随 y 时 匹配 x
// /小明(?=小红)/ 该正则理解为:小明后面一定跟随一个小红
console.log('小明小红'.match(/小明(?=小红)/));
// 先行否定断言
// 语法:x(?!y)
// x 后没有 y 跟随时 匹配 x
// /小明(?!小红)/ 该正则理解为:小明后面一定没有跟随小红
console.log('小明小芳'.match(/小明(?!小红)/));
// 后行断言
// 语法:(?<=y)x
// x 前有 y 则 匹配 x
// /小明(?=小红)/ 该正则理解为:小明的前面一定有一个小红
console.log('小红小明'.match(/(?<=小红)小明/));
// 后行否定断言
// 语法:(?<!y)x
// x 前没有 y 跟随时 匹配 x
// /小明(?=小红)/ 该正则理解为:小明前面一定没有跟随小红
console.log('小芳小明'.match(/(?<!小红)小明/));
</script>
第一个正则表达式:
<script>
// 正则表达式:用于匹配字符串的表达式
// 例如:可以写一个正则表达手,用于鉴定一个字符串是否是邮箱格式的字符串
// 声明一个正则表达式
// 方法一:
// abc@sdkf.com
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方法 用于检测字符串是否符合正则表达式的描述
// test 返回 true 代表 受测字符串符合正则表达式描述的特征
console.log(regex.test('abc@666.com'));
</script>
贪婪模式和非贪婪模式:
<script>
let str = '1234444'
let regex = /^1234+/
// match:从字符串中匹配出符合正则的字符串
let result = str.match(regex)
console.log(result);
// 贪婪模式:字符串在匹配正则表达式时,将尽可能多的匹配满足正则规则的字符
// 非贪婪模式:字符串在匹配正则表达式时,将尽可能少的匹配满足正则规则的字符
// 默认情况下字符串将执行贪婪模式的匹配
// 如何开启非贪婪模式
// 方法:在表示匹配个数的符号后面加上'?'问号
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));
</script>
正则表达式的全局模式:
<script>
let str = 'goods gods __goooods'
let regex = /go+ds/
// 不开全局模式 只会匹配到第一个符合条件的字符串
console.log(str.match(regex));
// 全局模式的正则表达式将匹配所有符合条件的字符串
// 在正则表达式的后面使用 g flag 来开启全局模式
// 另外一个 flag 是 i 代表 忽略大小写
// matchAll 匹配所有符合正则描述的字符串
// 参数必须是一个全局模式的正则表达式
regex = /go+ds/g
// 或 使用 RegExp
// 第二个参数就是 flag
regex = new RegExp('go+ds', 'gi')
let r = str.matchAll(regex)
console.log(r);
// 此处返回值 r 是一个 iterator(迭代器)
// 什么是迭代器?用于循环遍历一个集合的工具就是迭代器
// 迭代器一定包含一个 next 函数 代表取出下一个成员
// 所有的迭代器都可以使用forof来遍历
for (const item of r) {
console.log(item);
}
// 全局模式的另一个应用场景
// 字符串的替换函数
str = '共产党1 共产党2 共产党3'
regex = /共产党/
// replace 用于替换字符串
// 第一个参数:要替换的内容,是一个正则表达式
// 第二个参数:要换入的内容
// 返回值:替换后的字符串
r = str.replace(regex, '***');
console.log(r);
// 使用全局模式进行全局替换,否则只会替换第一个
regex = /共产党/g
r = str.replace(regex,'***')
console.log(r);
</script>
正则表达式中的特殊字符:
<script>
let regex = /^$/
// \d :10进制数
// \D :非10进制数
// \r :回车 \n :换行
// \s :所有不可见字符,制表符 回车换行 空格等
// \S : 所有可见字符
// . :基本等于任意字符 但不包括 \r\n
// 匹配任意字符任意次数的写法如下:
regex = /[\s\S]*/
// \t :制表符
</script>
字符串的基础操作:
<script>
let str = 'hello world !!!'
// 字符串可以被视为字符数组
// 查看字符串长度
console.log(str.length);
// 通过索引访问字符串中的字符
console.log(str[4]);
// charAt 函数可以获取指定索引出的字符
console.log(str.charAt(4)); // 等价于 str[4]
// split:分割字符串
// 参数:用于分割字符串的字符
// 返回值:字符串数组
let r = str.split(' ')
console.log(r);
// split + join 替换字符
// 例如:替换字符 *|& 为 _
str = 'hello*|&world*|&!!!'
r = str.split('*|&').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) // 从第一个 o 截取到 r
console.log(r);
// 第二个参数可以省略,如果只写一个参数,substring将从该参数位置一直截取到字符串末尾
r = str.substring(6)
console.log(r);
// indexOf:查询字符串中指定字符在字符串中的索引位置
// 参数:要查询的字符串
// 返回值:被查询字符串的索引
console.log(str.indexOf('o'));
// lastIndexOf
console.log(str.lastIndexOf('o'));
// 举例:截取字符串从 o ~ r
console.log(str.substring(str.indexOf('o'), str.lastIndexOf('r') + 1));
// startsWith:用于判断字符串是否以指定字符串开头
// 参数:指定开头的字符串
// 返回值:bool值 true代表是以指定字符串开头的,false代表不是
console.log(str.startsWith('hello'));
// endsWith:用于判断字符串是否以指定字符串结尾
console.log(str.endsWith('hello'));
// toUpperCase toLowerCase 将字符串中的英文转成全为大写或小写
str = str.toUpperCase()
console.log(str);
str = str.toLowerCase()
console.log(str);
// 例如:统计一个字符串中出现了多少个a字符,忽略大小写
str = 'djaLHFiuahkawengjlkaergiysadjAHda'
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);
</script>
字符串的match函数:
<script>
let str = 'goods_gods_gooooods'
let regex = /go{1,3}ds/
// match:从字符串中匹配出符合正则的字符串
// 参数:用于匹配的正则表达式
// 返回值:包含了匹配结果的数组
let r = str.match(regex)
console.log(r);
</script>
8.闭包和计时器
闭包:
index.html
<script src="./sum.js"></script>
<script src="./avg.js"></script>
<script>
// 什么是闭包
// 闭包也叫函数闭包,一个函数被另一个函数包裹并返回,包裹一些需要被保存的数据
// 且函数需要返回一个持续引用的对象,这就叫闭包
// 为了方便理解,做如下的假设:
// 1.假设有一个函数A
// 2.A内声明变量B
// 3.A返回一个 包含函数的内容
// 4.A返回的 包含的函数必须引用变量B
// 5.此时函数A就是闭包的
const demo = (function A() {
let B = { name: '张三' }
return function () {
// 函数内引用变量B 则函数A是闭包的
console.log(B);
}
// 当返回一个值时 函数A将返回 A中没被使用的变量将被垃圾回收掉,则A不是闭包的
// return 'hello world'
// 当返回的是直接声明的对象或数组的时候,声明时将B作为对象属性的值或数组成员传入对象或数组
// 当A返回后,变量B则没有再被引用了,则被垃圾回收掉,A不是闭包的
// return { name: B }
})()
// 自己写的例子
const aa = (function as(){
let bb = 0
return function(){
console.log(bb);
}
})()
aa()
// 应用场景
// 闭包用于存储一些不让函数外访问的数据,或者为了避免作用域中变量名的冲突,可以使用闭包
// 如何闭包
// 使用自调用函数,返回一个函数
const sayMessage = (() => {
// 声明一个用于记录调用次数的变量
// 闭包的函数空间内声明的变量,在内存中将持续存在
// 垃圾回收不会回收该变量
// 该变量在外部无法访问
let count = 0
// 在自调用函数中返回一个函数
// 返回的函数就是一个闭包后的函数
return (msg) => {
console.log(msg);
count++
console.log(`sayMessage 调用次数为 ${count}`);
}
})()
sayMessage('hello world')
console.log(sum([1, 2, 3]));
console.log(avg([4, 5, 6]));
const obj = (function () {
let name = '张三'
// 在函数空间内 可以返回任意的
return {
// 获取名称
getName() {
return name
},
// 改名
setName(newName) {
name = newName
}
}
})()
console.log(obj);
console.log(obj.getName());
obj.setName('李四')
console.log(obj.getName());
</script>
avg.js
// var _sum = 0
// // 求平均数
// // 参数是个数组
// function avg(arr) {
// _sum = arr.reduce(p, n => p + n)
// return _sum / arr.length
// }
// 求平均数
// 参数是个数组
const avg = (function () {
let _sum
return (arr) => {
_sum = arr.reduce((p, n) => p + n)
return _sum / arr.length
}
})()
sum.js
// var _sum = 0
// 求和
// 参数是个数组
// function sum(arr) {
// _sum = arr.reduce((p, n) => p + n)
// return _sum
// }
const sum = (()=>{
let _sum
return(arr) => {
_sum = arr.reduce((p, n) => p + n)
return _sum
}
})()
列表显示:
<body>
<div>
<table border="5px solid #000">
<thead>
<tr>
<th>姓名</th>
<th>学号</th>
<th>班级</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 编写一个待会被js使用的html模板 -->
<tr>
<td>1</td>
<td>张三</td>
<td>1班</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</body>
<script src="./api/db.js"></script>
<script>
// 数据增删改查的js练习
// 增删改查也叫做crud(Create,Read,Update,Delete)
// 学会数据的增删改查流程
// 此处使用localStorage来充当数据库
// 思路:
// 页面加载完成后,从服务器拉取数据(也就是在页面的 load 事件后 做一些初始化页面的操作)
// 创建一个 js 文件,充当发起网络请求的 api 文件
// 该例子,用到一个思路:用数据驱动页面——页面最终长什么样子,取决于数据
// 构造测试数据
// 假设该数据是从服务器得到
// let data = [
// {
// name: '张三', //姓名
// no: '0', //学号
// class: '1班' //班级
// },
// {
// name: '李四',
// no: '1',
// class: '2班'
// },
// {
// name: '王麻子',
// no: '2',
// class: '3班'
// }
// ]
let data = []
// 给 localStorage 填充数据
// localStorage['students'] = JSON.stringify(data)
const tbody = document.querySelector('tbody')
// 渲染函数,用于显示渲染页面内容
// 此处 render 中需要将 data 转换成 html 的内容
function render() {
// let html = ''
// data.forEach(item => {
// html +=
// `
// <tr>
// <td>${item.no}</td>
// <td>${item.name}</td>
// <td>${item.class}</td>
// <td>
// <button>编辑</button>
// <button>删除</button>
// </td>
// </tr>
// `
// })
// console.log(html);
// let html = data.map(item => `
// <tr>
// <td>${item.no}</td>
// <td>${item.name}</td>
// <td>${item.class}</td>
// <td>
// <button>编辑</button>
// <button>删除</button>
// </td>
// </tr>
// `)
// console.log(html);
// // 再使用 join 函数拼接
// html = html.join('')
// console.log(html);
// }
let html = data.map(item => `
<tr>
<td>${item.no}</td>
<td>${item.name}</td>
<td>${item.class}</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr>
`).join('')
console.log(html);
// 插入文档
tbody.innerHTML = html
}
// render()
// 给窗口对象绑定load事件
// 当窗口加载完成后,执行事件回调
window.addEventListener('load',() => {
let r = query()
// r 存在就赋值 data
r && (data = r)
render()
})
</script>
添加数据:
<body>
<!-- 工具栏 -->
<div><button class="add-btn">添加</button></div>
<div>
<table border="5px solid #000">
<thead>
<tr>
<th>姓名</th>
<th>学号</th>
<th>班级</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 编写一个待会被js使用的html模板 -->
<tr>
<td>1</td>
<td>张三</td>
<td>1班</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</body>
<script src="./api/db.js"></script>
<script src="./utils.js"></script>
<script>
// 总结:
// 通过点击按钮,跳转到一个表单页,进行数据添加
// 添加成功后,返回列表页,获取新数据
// 表单中,通常要做表单验证,验证用户输入的合法性
let data = []
// 给 localStorage 填充数据
// localStorage['students'] = JSON.stringify(data)
const tbody = document.querySelector('tbody')
// 渲染函数,用于显示渲染页面内容
// 此处 render 中需要将 data 转换成 html 的内容
function render() {
// let html = ''
// data.forEach(item => {
// html +=
// `
// <tr>
// <td>${item.no}</td>
// <td>${item.name}</td>
// <td>${item.class}</td>
// <td>
// <button>编辑</button>
// <button>删除</button>
// </td>
// </tr>
// `
// })
// console.log(html);
// let html = data.map(item => `
// <tr>
// <td>${item.no}</td>
// <td>${item.name}</td>
// <td>${item.class}</td>
// <td>
// <button>编辑</button>
// <button>删除</button>
// </td>
// </tr>
// `)
// console.log(html);
// // 再使用 join 函数拼接
// html = html.join('')
// console.log(html);
// }
let html = data.map(item => `
<tr>
<td>${item.no}</td>
<td>${item.name}</td>
<td>${item.class}</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr>
`).join('')
console.log(html);
// 插入文档
tbody.innerHTML = html
}
// 添加数据
$('.add-btn').addEventListener('click', () => {
// 跳转到新增数据页
location.href = './add.html'
})
// 给窗口对象绑定load事件
// 当窗口加载完成后,执行事件回调
window.addEventListener('load', () => {
let r = query()
// r 存在就赋值 data
r && (data = r)
render()
})
</script>
删除单条数据:
<body>
<!-- 工具栏 -->
<div><button class="add-btn">添加</button></div>
<div>
<table border="5px solid #000">
<thead>
<tr>
<th>姓名</th>
<th>学号</th>
<th>班级</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 编写一个待会被js使用的html模板 -->
<tr>
<td>1</td>
<td>张三</td>
<td>1班</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</body>
<script src="./api/db.js"></script>
<script src="./utils.js"></script>
<script>
// 总结:
// 删除时,为了让程序知道用户要删除哪一个数据,那么需要给按钮添加一个 数据唯一标识符 参数
// 点击按钮时,可以获取对应的数据唯一标识符
// 删除是危险操作,所以需要询问用户 甚至要做验证
let data = []
const tbody = document.querySelector('tbody')
function render() {
// 添加删除事件的一种方法 <button onclick="removeOne('${item.no}')">删除</button>
let html = data.map(item => `
<tr>
<td>${item.no}</td>
<td>${item.name}</td>
<td>${item.clazz}</td>
<td>
<button>编辑</button>
<button class="remove-btn" no="${item.no}">删除</button>
</td>
</tr>
`).join('')
console.log(html);
// 插入文档
tbody.innerHTML = html
}
// 给页面函数添加事件
function addEvents() {
// 查询所有删除按钮
const removeBtns = document.querySelectorAll('.remove-btn')
for (let i = 0; i < removeBtns.length; i++) {
const removeBtn = removeBtns[i];
// 给每个按钮绑定点击事件
removeBtn.addEventListener('click', function () {
// 使用 function 定义的此处的事件处理程序 则 this 代表被点击的按钮
let no = this.getAttribute('no')
// 由于删除是一个危险操作,所以需要提示确认
confirm('确定删除吗') && removeOne(no)
// 刷新页面
location.reload()
})
}
}
// 添加数据
$('.add-btn').addEventListener('click', () => {
// 跳转到新增数据页
location.href = './add.html'
})
// 给窗口对象绑定load事件
// 当窗口加载完成后,执行事件回调
window.addEventListener('load', () => {
let r = query()
// r 存在就赋值 data
r && (data = r)
render()
addEvents()
})
</script>
批量删除数据:
<body>
<div>
<button class="add-btn">添加</button>
<button class="removeAll">批量删除</button>
</div>
<div>
<table border="5px solid #000">
<thead>
<tr>
<th><label><input class="select-all" type="checkbox">全选</label></th>
<th>姓名</th>
<th>学号</th>
<th>班级</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td><input class="checkbox" type="checkbox"></td>
<td>1</td>
<td>张三</td>
<td>1班</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</body>
<script src="./api/db.js"></script>
<script src="./utils.js"></script>
<script>
let data = []
// 用于存储当前被勾选的学号值
let selection = []
const tbody = document.querySelector('tbody')
function render() {
let html = data.map(item => `
<tr>
<td><input class="checkbox" type="checkbox" no="${item.no}"></td>
<td>${item.no}</td>
<td>${item.name}</td>
<td>${item.clazz}</td>
<td>
<button>编辑</button>
<button class="remove-btn" no="${item.no}">删除</button>
</td>
</tr>
`).join('')
tbody.innerHTML = html
}
function addEvents() {
const removeBtns = document.querySelectorAll('.remove-btn')
for (let i = 0; i < removeBtns.length; i++) {
const removeBtn = removeBtns[i];
removeBtn.addEventListener('click', function () {
let no = this.getAttribute('no')
confirm('确定删除吗') && removeOne(no)
location.reload()
})
}
// checkbox 点击事件
const checkboxs = document.querySelectorAll('.checkbox')
for (let i = 0; i < checkboxs.length; i++) {
const checkbox = checkboxs[i];
// 绑定元素的变化事件
// 当 checkbox 的选择状态发生变化时触发该事件
checkbox.addEventListener('change', function () {
// 获取学号
let no = this.getAttribute('no')
// 获取当前勾选状态
let checked = this.checked
if (checked) {
// 判断数组中不存在学号,则添加数据
!selection.includes(no) && selection.push(no)
} else {
// 判断数组中已存在学号,则删除学号
let i = selection.findIndex(item => item === no)
// 当索引不为 -1 说明 findIndex 查找到了数据 则删除该数据
i !== -1 && selection.splice(i, 1)
}
})
}
// 全选按钮
$('.select-all').addEventListener('change', function () {
// 获取当前的勾选状态
let checked = this.checked
// if (checked) {
// // 执行全选逻辑
// for (let i = 0; i < checkboxs.length; i++) {
// const checkbox = checkboxs[i];
// // 判断checkbox是否勾选,没勾选的就勾选
// !checkbox.checked && checkbox.click()
// }
// } else {
// for (let i = 0; i < checkboxs.length; i++) {
// const checkbox = checkboxs[i];
// // 判断checkbox是否勾选,已勾选的就取消勾选
// checkbox.checked && checkbox.click()
// }
// }
// 优化代码
for (let i = 0; i < checkboxs.length; i++) {
const checkbox = checkboxs[i];
// 当全选按钮的状态不等于当前 checkbox 的状态
// 则点击当前 checkbox
(checked !== checkbox.checked) && checkbox.click()
}
})
}
// 工具栏事件
$('.add-btn').addEventListener('click', () => {
location.href = './add.html'
})
// 全部删除
$('.removeAll').addEventListener('click', () => {
// 先判断 selection 中存在选项吗,不存在则不执行
if (selection.length === 0) return
// 询问成功后删除
confirm('确定删除吗?') && removeAll(selection)
location.reload()
})
window.addEventListener('load', () => {
let r = query()
r && (data = r)
render()
addEvents()
})
</script>
数据编辑:
<body>
<div>
<button class="add-btn">添加</button>
<button class="removeAll">批量删除</button>
</div>
<div>
<table border="5px solid #000">
<thead>
<tr>
<th><label><input class="select-all" type="checkbox">全选</label></th>
<th>姓名</th>
<th>学号</th>
<th>班级</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td><input class="checkbox" type="checkbox"></td>
<td>1</td>
<td>张三</td>
<td>1班</td>
<td>
<button>编辑</button>
<button>删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</body>
<script src="./api/db.js"></script>
<script src="./utils.js"></script>
<script>
let data = []
let selection = []
const tbody = $('tbody')
function render() {
let html = data.map(item => `
<tr>
<td><input class="checkbox" type="checkbox" no="${item.no}"></td>
<td>${item.no}</td>
<td>${item.name}</td>
<td>${item.clazz}</td>
<td>
<button class="edit-btn" no="${item.no}">编辑</button>
<button class="remove-btn" no="${item.no}">删除</button>
</td>
</tr>
`).join('')
tbody.innerHTML = html
}
function addEvents() {
const removeBtns = document.querySelectorAll('.remove-btn')
for (let i = 0; i < removeBtns.length; i++) {
const removeBtn = removeBtns[i];
removeBtn.addEventListener('click', function () {
let no = this.getAttribute('no')
confirm('确定删除吗') && removeOne(no)
location.reload()
})
}
const checkboxs = document.querySelectorAll('.checkbox')
for (let i = 0; i < checkboxs.length; i++) {
const checkbox = checkboxs[i];
checkbox.addEventListener('change', function () {
let no = this.getAttribute('no')
let checked = this.checked
if (checked) {
!selection.includes(no) && selection.push(no)
} else {
let i = selection.findIndex(item => item === no)
i !== -1 && selection.splice(i, 1)
}
})
}
$('.select-all').addEventListener('change', function () {
let checked = this.checked
for (let i = 0; i < checkboxs.length; i++) {
const checkbox = checkboxs[i];
(checked !== checkbox.checked) && checkbox.click()
}
})
// 编辑按钮
const editBtns = document.querySelectorAll('.edit-btn')
for (let i = 0; i < editBtns.length; i++) {
const editBtn = editBtns[i];
editBtn.addEventListener('click', function () {
let no = this.getAttribute('no')
// 跳转到编辑页 并将学号作为参数
// 参数用问号追加到地址的末尾
// ?a=1&b=2 这种格式的参数名为 queryString (查询字符串)
location.href = './edit.html?no=' + no
})
}
}
$('.add-btn').addEventListener('click', () => {
location.href = './add.html'
})
$('.removeAll').addEventListener('click', () => {
if (selection.length === 0) return
confirm('确定删除吗?') && removeAll(selection)
location.reload()
})
window.addEventListener('load', () => {
let r = query()
r && (data = r)
render()
addEvents()
})
</script>
导致内存溢出的情况:
<script>
// 内存溢出
// 当内存中堆栈内容已经装不下了,再给内存增加内容则溢出了
// 栈溢出
// 无限的函数递归会引发栈溢出
function fn1() {
fn1()
}
// fn1()
// 堆溢出
// let obj = {name:'zhangsan'}
let count = 0
// while (true) {
// // 每次循环都声明一个新的对象
// // 对象保存在堆上
// // 当堆上内容过多 则会导致堆溢出
// let a = { name: Date() }
// }
</script>
计时器:
<body>
<button class="btn1">停止 timeout 计时</button>
<button class="btn2">停止 interval 计时</button>
</body>
<script>
// 计时器
// 什么是计时器?当经过指定时间后触发一段代码的函数就是一个计时器
// 声明一个计时器:setTimeout
// 第一个参数:计时器计时结束后触发的函数
// 第二个参数:计时时长,单位:毫秒
// 返回值:计时器id
// 计时器id 用于停止计时
let timerId = setTimeout(() => {
console.log('hello setTimeout');
}, 3000)
// clearTimeout
document.querySelector('.btn1').addEventListener('click', () => {
// clearTimeout 清空计时器
// 参数是 计时器id
// 清空后计时器将被取消掉
clearTimeout(timerId)
})
// setInterval 循环计时函数
// setInterval 函数,每次经过指定时间,触发一次指定的函数
// 参数和返回值 与 setTimeout 相同
// let count = 0
// let timerId2 = setInterval(() => {
// count++
// console.log(count);
// }, 1000)
// clearInterval
// document.querySelector('.btn2').addEventListener('click', () => {
// // 清空循环计时器
// clearInterval(timerId2)
// })
// 请避免以下死循环
// setTimeout 死循环
// let count = 0
// function countTime() {
// setTimeout(() => {
// count++
// console.log(count);
// // 递归调用自身 形成死循环
// countTime()
// // 添加条件来跳出循环 则不会导致内存溢出
// count < 10 && countTime()
// }, 5000);
// }
// countTime()
// setInterval 死循环
// setInterval(() => {
// count++
// console.log(count);
// })
// 关于 setTimeout 和 setInterval 中的异常
// function countTime() {
// setTimeout(() => {
// if (count === 5) console.log(abc);
// count++
// console.log(count);
// count < 10 && countTime()
// }, 1000);
// }
// countTime()
// setTimeout 计时器内出现异常 停止递归
// setInterval(() => {
// if (count === 5) console.log(abc.obj());
// count++
// console.log(count);
// }, 1000)
// setInterval 计时器内出现异常 程序继续执行
// 注意事项:
// 1.尽量不使用 setInterval
// 理由:setInterval 可能由于人为原因忘了关闭,或者内部出现异常,导致代码死循环
// 2. 若要做大量循环调用甚至是无限循环调用时(例如轮询死循环调用),请使用 setInterval 而不是使用 setTimeout 递归进行循环
// 理由:setTimeout 会占用大量内存堆栈
</script>
9.js移动与渐入渐出动画
封装移动函数:
<style>
.box {
width: 100px;
height: 100px;
background-color: #f00;
position: relative;
left: 500px;
top: 200px;
}
</style>
<body>
<div class="box"></div>
</body>
<script>
// 思路
// 一定是使用计时器来播放动画
// 步骤如下:
// 声明移动速度
// 使用计时器,每帧让元素移动指定速度的距离
// 在每一帧移动后,判断是否到达了终点位置,若已经到达了终点位置,应该停止计时器
// 若最后一帧位置超出了终点位置,应该将元素设置到终点位置
// 封装移动函数
// 函数用来封装不变的内容,变化的内容就成为参数
// 参数:
// 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)
</script>
渐入渐出动画:
<style>
img {
width: 400px;
height: 300px;
}
</style>
<body>
<img style="opacity: 1;" src="./滚动相片练习/img/an1.jpg">
</body>
<script>
const img = document.querySelector('img')
let timerId
// 绑定鼠标移入移出事件
// 进入事件
img.addEventListener('mouseenter', () => {
console.log('enter');
timerId = setOpacity(img, Number(img.style.opacity), -0.01, 10)
})
// 离开事件
img.addEventListener('mouseleave', () => {
console.log('leave');
clearTimeout(timerId)
timerId = setOpacity(img, Number(img.style.opacity), 0.01, 10)
})
// 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.opacity = 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
}
</script>
元素移动:
<style>
.box {
width: 100px;
height: 100px;
background-color: #f00;
position: relative;
left: 0;
}
</style>
<body>
<div class="box"></div>
</body>
<script>
// 移动元素的思路
// 使用计时器 每隔一段时间 让元素位置产生一个增量的变化
const box = document.querySelector('.box')
let timerId = setInterval(() => {
// 一个动画帧
// 先运行逻辑运算
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);
</script>
10.时间对象和数学函数
时间对象:
<script>
// 参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date
// 时间字符串的参考:https://www.w3.org/TR/NOTE-datetime
// 时间对象 Date
// 时间数据如何表示?如何创建一个时间数据
// 表示时间的字符串
// 字符串需要满足 w3c 联盟要求的时间字符串
// 例如:1990-01-01
// 表示时间的数字
// 含义:表示格林威治1970-1-1开始以后,经过的毫秒时间
// 创建时间对象的方法
// 语法:new Date(params)
// 1.当前系统时间
let date = new Date()
console.log(date);
// Date 时间对象默认修改了对象的 toString 方法 所以转字符串时,会显示成时间字符串
// 所有对象都有 toString 方法,转换字符串时,js会调用该方法
// 2.创建指定时间字符串
// 不推荐使用,因为这是个非标准时间字符串,不同浏览器解析时间字符串的方式不同
date = new Date('1999-02-01')
console.log(date);
// 3.通过格林威治毫秒时创建时间
date = new Date(1000 * 60 * 60 * 24 * 365)
console.log(date);
// 4.通过年月日时分秒创建时间
// 参数分别代表 年月日时分秒
// 注意:月份是从0开始计算的
date = new Date(1999, 2, 28, 18, 23, 58)
console.log(date);
// 参数至少写前两个参数,后续参数可以省略
date = new Date(1999, 2, 28, 18)
console.log(date);
date = new Date(1999, 2, 28)
console.log(date);
date = new Date(1999, 2)
console.log(date);
// 如何读取时间?如何设置时间?
// 读取时间
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()); // 毫秒 值不会大于1000
date = new Date(2021, 12, 2)
console.log(date);
console.log(date.getDay());
// 设置时间
date.setFullYear(2023)
date.setMonth(0)
date.setDate(5)
date.setHours(20)
date.setMinutes(66)
date.setSeconds(67)
date.setMilliseconds(1127)
console.log(date);
// 其他常用时间函数
// Date.now() // 获取当前系统时间的格林威治毫秒时
console.log(Date.now());
// date.getTime() // 获取date对象代表的格林威治毫秒时
console.log(date.getTime());
// 通常来说日期对象需要转换成字符串显示,否则用户看不懂
function format(date) {
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}`
}
console.log(format(new Date()));
</script>
数学函数:
<script>
// 数学函数:专门用于处理数学运算的函数
// 数学函数可以通过浏览器内置对象Math进行直接调用
// 参考:
// 三角函数
// 需要注意的是,三角函数 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:获取数字的绝对值
console.log(Math.abs(100));
console.log(Math.abs(-100));
// ceil:一个小数向上取整
console.log(Math.ceil(5.1));
console.log(Math.ceil(5.2));
console.log(Math.ceil(5.5));
console.log(Math.ceil(5.9));
// floor:一个小数向下取整
console.log(Math.floor(5.1));
console.log(Math.floor(5.2));
console.log(Math.floor(5.5));
console.log(Math.floor(5.9));
// round:四舍五入
console.log(Math.round(5.1));
console.log(Math.round(5.2));
console.log(Math.round(5.5));
console.log(Math.round(5.9));
// max:取参数中较大数
console.log(Math.max(1, 2, 3, 4, 5, 6, 7));
// min:取参数中较小数
console.log(Math.min(1, 2, 3, 4, 5, 6, 7));
// 通过max和min写一个定界函数
// value:要定界的数字
// min,max 最小和最大值
// 返回值:value < min 时返回 min , value > max 时返回 max , min < value < 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)
}
console.log(clamp(1, 0, 2));
console.log(clamp(-1, 0, 2));
console.log(clamp(3, 0, 2));
// pow:返回x的y次方
// 语法:Math.pow(x,y)
console.log(Math.pow(2, 3));
// sqrt:返回一个数的平方根
console.log(Math.sqrt(25));
// random:取随机数,范围在[0~1)之间,能取到0但取不到1
console.log(Math.random());
// 假设随机一个 50 ~ 100 的数
console.log(Math.random() * 50 + 50); // => 取不到100
console.log(Math.round(Math.random() * 50) + 50); // => 能取到100
// 随机一个 n ~ m 的数
// 则公式为:n + Math.random() * (m - n)
// sign:取符号
console.log(Math.sign(-135));
console.log(Math.sign(-51));
console.log(Math.sign(45));
console.log(Math.sign(29));
</script>
11.事件和异常处理
事件:
事件的基础:
<script>
// 什么是事件?
// 事件就是某种事情
// 在js中,事件就是:当某种情况发生的时候,能够触发一段代码,这个发生的情况就是事件
// 简单的理解,事件就是:(当。。。时,就。。。)
// 事件在js中以对象形式存在
// 什么是绑定事件?
// 将自定义的函数和某个事件进行捆绑,绑定后的效果就是:(当。。。时,发生的时候,就会触发我们绑定的函数)
// 绑定事件的方法:
// 使用元素对象的addEventListener函数进行事件绑定
// 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('按钮被点击了')
}
</script>
事件分类:
<style>
.box {
background-color: #f00;
width: 100px;
height: 100px;
}
.box2 {
background-color: #ff0;
width: 100px;
height: 100px;
position: absolute;
top: 100px;
left: 100px;
}
.boxContainer {
width: 500px;
height: 500px;
background-color: #0f0;
position: relative;
}
.box3 {
background-color: #00f;
width: 100px;
height: 100px;
}
.frame {
border: 5px solid #f00;
overflow: hidden;
}
</style>
<body>
<img src="./img/an15631.jpg">
<input type="text">
<input type="text">
<!-- 给一个非标单元素添加 tabindex 让他获得获取焦点的能力 从而可以触发 focus 和 blur 事件 -->
<div class="box" tabindex="0"></div>
<div class="boxContainer">
<div class="box2"></div>
</div>
<div class="box3" draggable="true"></div>
<div class="frame">
<div style="height: 3000px;"></div>
</div>
</body>
<script>
// 资源事件
const img = document.querySelector('img')
// load 加载完成
img.addEventListener('load', () => {
console.log('加载完成');
})
// error 加载失败
img.addEventListener('error', () => {
console.log('加载失败');
})
// 焦点事件
const input = document.querySelector('input')
const box = document.querySelector('.box')
// focus 获取焦点事件
input.addEventListener('focus', () => {
console.log('获取焦点');
})
box.addEventListener('focus', () => {
console.log('获取焦点');
})
// blur 失去焦点
input.addEventListener('blur', () => {
console.log('失去焦点');
})
box.addEventListener('blur', () => {
console.log('失去焦点');
})
// 鼠标事件
const box2 = document.querySelector('.box2')
// 鼠标左键单击事件
box2.addEventListener('click', () => {
console.log('单击左键');
})
// 右键菜单
box2.addEventListener('contextmenu', () => {
console.log('右键菜单');
})
// 双击
box2.addEventListener('dblclick', () => {
console.log('双击');
})
// 鼠标点下
box2.addEventListener('mousedown', ev => {
console.log('点下');
console.log(ev);
// ev.button 用于区分点击的是哪个键
// 0:左键
// 1:中键
// 2:右键
console.log(ev.button);
})
// 鼠标抬起
box2.addEventListener('mouseup', ev => {
console.log('抬起');
console.log(ev);
console.log(ev.button);
})
// 进入和离开事件
// box2.addEventListener('mouseenter', () => {
// console.log('进入');
// })
// box2.addEventListener('mouseleave', () => {
// console.log('离开');
// })
// 悬停和出去
// box2.addEventListener('mouseover', () => {
// console.log('悬停');
// })
// box2.addEventListener('mouseout', () => {
// console.log('出去');
// })
// 移动
// box2.addEventListener('mousemove', ev => {
// console.log('移动');
// console.log(ev);
// // offsetX offsetY 是鼠标相对于元素与左上角的坐标
// console.log(ev.offsetX);
// console.log(ev.offsetY);
// })
// 滚轮事件
box2.addEventListener('wheel', ev => {
console.log('鼠标滚轮');
console.log(ev);
// deltaY 纵向滚动的变化量
// 正数向下 负数向上
console.log(ev.deltaY);
})
// 拖动事件
// 元素上需要添加 draggable="true"
const box3 = document.querySelector('.box3')
// 拖动
box3.addEventListener('drag', ev => {
console.log('drag');
console.log(ev);
})
// 开始拖动时触发
box3.addEventListener('dragstart', ev => {
console.log('drag start');
console.log(ev);
})
// 结束拖动时触发
box3.addEventListener('dragend', ev => {
console.log('drag end');
console.log(ev);
})
// 媒体事件:和多媒体播放相关事件
// 表单元素事件
// 输入事件
// input.addEventListener('input', ev => {
// console.log('输入');
// console.log(ev);
// // 本次输入的内容
// console.log(ev.data);
// // 当前输入框的值
// console.log(ev.currentTarget.value);
// console.log(ev.target.value);
// })
// 变化事件
// 一般除了输入框外都使用 change 事件
// input.addEventListener('change', ev => {
// console.log('变化');
// console.log(ev);
// // 当前输入框的值
// console.log(ev.currentTarget.value);
// console.log(ev.target.value);
// })
// 按键事件
// 按下
// 可以按住不放持续触发事件
input.addEventListener('keydown', ev => {
console.log('按下');
console.log(ev);
// 获取用户点击的哪个按键
console.log(ev.key);
console.log(ev.keyCode);
})
box.addEventListener('keydown', () => {
console.log('box按下');
})
// 抬起
input.addEventListener('keyup', ev => {
console.log('抬起');
console.log(ev);
// 获取用户点击的哪个按键
console.log(ev.key);
console.log(ev.keyCode);
})
// 按住不放(按压)
input.addEventListener('keypress', ev => {
console.log('按压');
console.log(ev);
})
// 补充:
// 窗口重置大小
window.addEventListener('resize', () => {
console.log('resize');
console.log(window.innerWidth);
console.log(window.innerHeight);
})
// 滚动条滚动事件
// 要监听滚动条的滚动事件,要先找到产生滚动条的元素
// window.addEventListener('scroll', ev => {
// console.log('scroll');
// console.log(ev);
// })
document.querySelector('.frame').addEventListener('scroll', function (ev) {
console.log('scroll');
console.log(ev);
// this 代表当前触发事件的对象
// this.scrollTop 代表纵向滚动的高度
console.log(this.scrollTop);
})
</script>
事件机制——冒泡:
<body>
<div>
<button>点击</button>
</div>
</body>
<script>
// 在html中触发事件的元素,将会把事件不断地向父元素传递,这个过程叫做冒泡
document.querySelector('button').addEventListener('click', () => {
console.log('button');
})
document.querySelector('div').addEventListener('click', () => {
console.log('div');
})
document.querySelector('body').addEventListener('click', () => {
console.log('body');
})
</script>
事件机制——捕获事件:
<body>
<div>
<button>点击</button>
</div>
</body>
<script>
// 事件捕获
// 事件触发后,可以由上级元素先处理事件,这个过程就是捕获事件
document.querySelector('button').addEventListener('click', () => {
console.log('button');
})
document.querySelector('div').addEventListener('click', () => {
console.log('div');
})
// addEventListener 的第三个参数代表是否捕获事件
// 捕获事件的元素会先处理事件,然后将事件对象还给产生事件的元素,然后正常冒泡
document.body.addEventListener('click', () => {
console.log('body');
}, true)
</script>
阻止事件冒泡:
<body>
<div>
<button>点击</button>
</div>
</body>
<script>
document.querySelector('button').addEventListener('click', ev => {
// 使用事件对象的 stopPropagation 来阻止事件冒泡
// ev.stopPropagation()
console.log('button');
})
document.querySelector('div').addEventListener('click', () => {
console.log('div');
})
document.body.addEventListener('click', () => {
console.log('body');
})
</script>
屏蔽默认事件:
<style>
.box{
width: 100px;
height: 100px;
background-color: #f00;
}
</style>
<body>
<!-- <form onsubmit="return false"> -->
<form>
<button>提交</button>
</form>
<!-- <div class="box" oncontextmenu="return false"></div> -->
<div class="box"></div>
</body>
<script>
// 方法一:使用事件对象屏蔽元素事件的默认行为
document.querySelector('form').addEventListener('click', ev => {
// preventDefault 屏蔽当前元素默认的事件行为
ev.preventDefault()
console.log('提交');
})
document.querySelector('.box').addEventListener('contextmenu', ev => {
ev.preventDefault()
console.log('右键');
})
// 方法二:对应事件属性 return false 也能屏蔽默认行为
</script>
事件对象的currentTarget和target:
<body>
<div>
<button>点击</button>
</div>
</body>
<script>
//
const div = document.querySelector('div')
const btn = document.querySelector('button')
// target:代表产生事件的元素
// currentTarget:当前处理事件的元素
// 注意 currentTarget 是变化的 可以改动
btn.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);
})
</script>
通过js代码来触发事件:
<style>
.box {
width: 100px;
height: 100px;
background-color: #f00;
}
</style>
<body>
<div class="box"></div>
<button class="btn1">调用函数触发事件</button>
<button class="btn2">发出事件对象</button>
</body>
<script>
// 通过js 代码来触发元素事件的方法有两个
const box = document.querySelector('.box')
box.addEventListener('click', ev => {
console.log('click');
})
box.addEventListener('contextmenu', ev => {
// 用 preventDefault 取消事件
ev.preventDefault()
console.log('menu');
})
document.body.addEventListener('contextmenu', ev => {
console.log('body contextmenu');
})
// 方法一:通过对应事件名的函数进行触发
// 缺点:不是所有事件都有对应的方法来触发
document.querySelector('.btn1').addEventListener('click', ev => {
console.dir(box)
// 调用 click 来触发点击事件
box.click()
})
// 方法二:发出事件对象
// 步骤:
// 1.创建事件对象
// 2.调用要触发该事件的 dom 的 dispatchEvent 方法
document.querySelector('.btn2').addEventListener('click', ev => {
// 事件对象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 用来派发一个事件
// 返回值是个bool值,true 代表事件没被取消,false 代表事件被取消(调用了 preventDefault)
let r = box.dispatchEvent(event)
console.log(r);
})
</script>
自定义事件对象:
<style>
.box {
width: 100px;
height: 100px;
background-color: #f00;
}
</style>
<body>
<div class="box"></div>
<button>发出自定义事件</button>
</body>
<script>
// 发出自定义事件的方法:
// 1.给需要接受自定义事件的元素绑定自定义事件
// 2.创建一个自定义事件
// 3.调用需要接收自定义事件的 dom 的 dispatchEvent
// 1.给需要接受自定义事件的元素绑定自定义事件
document.body.addEventListener('open-my-eyes', ev => {
console.log('body my eyes open');
console.log(ev);
})
const box = document.querySelector('.box')
box.addEventListener('open-my-eyes', ev => {
ev.preventDefault()
console.log('box my eyes open');
console.log(ev);
})
document.querySelector('button').addEventListener('click', ev => {
// 2.创建一个自定义事件
// CustomEvent 参数和 Event 的参数基本相同
const ce = new CustomEvent('open-my-eyes', {
bubbles: false,
cancelable: false,
// 自定义的事件参数
// 该参数可以传递任意内容
detail: { name: '张三' }
})
// 3.调用需要接收自定义事件的 dom 的 dispatchEvent
let r = box.dispatchEvent(ce)
console.log(r);
})
</script>
给div元素增加keydown和focus事件:
<style>
div {
width: 500px;
height: 500px;
background-color: gold;
}
</style>
<body>
<!-- 在div上加入一个tabindex,可以让div成为一个表单元素 -->
<!-- tabindex 可以指定使用tab按键切换焦点元素时的顺序,顺序由大到小排列 -->
<div tabindex="0"></div>
<input tabindex="1">
</body>
<script>
let div = document.querySelector('div')
div.addEventListener('keydown', ev => {
console.log('keydown')
}, true)
div.addEventListener('focus', ev => {
console.log('focus')
})
</script>
异常处理:
基本吃力方法和异常的特点:
<script>
// 异常处理
// 什么是异常?当程序运行时,运行不下去了,碰到了问题,该问题就是一个异常
// 异常的特点:当程序抛出异常后,代码将停止运行
// 什么是异常处理?当程序出现异常时,开发人员进行的一个手动处理
// 异常处理的方法叫做捕获异常
// 只有运行时异常(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');
</script>
finally:
<script>
// 什么是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, 0)
console.log(r);
} catch (e) {
console.error(e);
} finally {
// try catch 后 最终一定会执行的程序
console.log('无论try执行成功还是抛出异常,都会执行此处的代码');
}
</script>
自定义异常对象:
<script>
// 什么是自定义异常?
// 开发人员自己创建的异常类就是自定义异常
// 作用:给异常分类,告诉开发人员哪些异常是需要处理的,哪些是系统异常不需要处理
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 Error('除数不能为0')
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('abc', 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);
}
}
</script>
12.面向对象编程
对象:
<script>
// 什么是对象,js用属性和行为来描述某个物体而产生的一种数据结构,该数据成为对象
// 声明对象
// 设计一个对象最重要的就是设计它的属性和行为
// 属性:都是一些值
// 行为:都是一些函数
let duck = {
// 设计对象属性如下
color: '#0f0',
age: 2,
name: '和昊',
// 对象行为
// 对象的行为一般称为方法
swim() {
console.log(`鸭桑${this.name}在游泳`);
},
shout() {
console.log('笑啦');
},
fly() {
console.log('极巨飞冲');
}
}
// 调用对象属性和行为
console.log(duck.name);
duck.fly()
</script>
类和实例对象:
<script>
// 什么是类 class
// 用于描述同类事物的类型叫做类,类型是抽象的
// 构造函数:用于构造类实例的函数
// 声明类型
// 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 语法定义类型如下:
class Car {
// 属性
// 价格
value = 0
// 生产厂商
producer = ''
// 名称
name = ''
// 声明构造函数
// 用来初始化对象
constructor(value, producer, name) {
this.value = value
this.producer = producer
this.name = name
}
// 行为
drive() {
console.log(`${this.producer}产的 价值 ${this.value} 的 ${this.name} 在飞驰`);
}
}
// 实例化类型
// 生成一个类型的个例的过程称为实例化
// 以下代码中 new Human() 这句话就是在实例化 Human 类
// 实例化的结果被存在了 zhangSan 变量中,
// 我们称实例化的结果为:实例,实例对象,类实例
// new 关键字用于实例化类型
// new 关键字后面的 Human() 实际上是在调用构造函数
let zhangSan = new Human('张三', 17, 'male')
console.log(zhangSan);
// 修改实例属性
zhangSan.age = 24
console.log(zhangSan);
// 调用实例行为
zhangSan.eat('小黄瓜')
let car = new Car(2100000, 'Nissan', 'GTR')
console.log(car);
console.log(car.value);
car.drive()
// 实例和类的关系是:
// 类是实例的模板,实例是类的成品
// 访问实例属性和行为
</script>
判断对象是否包含某个key:
<script>
let obj = { a: 1, b: 2, c: '' }
// 方法一:使用 boolean 值的隐式转换
if (obj.a) {
// 若存在就执行if里的代码块
console.log('ok');
}
// 甚至可以用来判断对象是否存在
if (obj) {
console.log('存在');
}
// obj = null
// obj = undefined
// if (obj) {
// console.log('存在');
// }
// 上述方法的缺点是:若 c 的值是空字符串,0或者 false 都会被认为“c 不存在”,然而 c 这个 key 是存在的
if (obj.c) {
console.log('c 存在');
}
// 方法二:使用 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 存在');
}
// 方法四的缺点是:必须要预先知道 c 的类型才可以进行检测
// 直接判断不是 null 和 undefined
if (typeof obj.c !== 'null' && typeof obj.c !== 'undefined') {
console.log('c 存在');
}
// 总结:
// 1.绝对不会错的用法是 方法二 和 方法三
// 2.快速开发使用 方法一 但要注意其缺点
</script>
声明私有属性和私有方法:
<script>
// 什么是私有属性和方法?
// 在实例对象内能使用但是实例对象外无法使用的属性和方法,成为私有属性和方法
function Human(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
// 声明一个变量来充当私有属性
let hobbie = '败家之眼'
this.eat = function (food) {
console.log(`${this.name} 正在吃 ${food}`);
console.log(hobbie);
}
// 私有方法
const fn = function () {
console.log('这是私有方法');
}
}
class Car {
value = 0
producer = ''
name = ''
// 私有方法或属性在名称的前面使用 # 井号
#engine = '这是发动机'
constructor(value, producer, name) {
this.value = value
this.producer = producer
this.name = name
}
drive() {
console.log(`${this.producer}产的 价值 ${this.value} 的 ${this.name} 在飞驰`);
console.log(this.#engine);
this.#error()
}
#error() {
console.error('这是私有的方法');
}
}
let zhangSan = new Human('张三', 17, 'male')
console.log(zhangSan.hobbie);
// zhangSan.eat('东西')
// zhangSan.fn()
let car = new Car(1000, 'ok', 'good')
// console.log(car.#engine);
car.drive()
// car.#error() // 编译未通过
</script>
通过面向对象渲染一个表:
<style>
.clock {
border: 5px solid #000;
border-radius: 10px;
padding: 8px;
display: inline-block;
}
.red {
color: #f00;
}
.green {
color: #0f0;
}
.blue {
color: #00f;
}
</style>
<body>
<!-- html 模板 -->
<!-- <div class="clock">
<span class="red">23</span>:<span class="green">59</span>:<span class="blue">46</span>
</div> -->
</body>
<script>
class Clock {
hour // 小时
minute // 分钟
second // 秒
#timer // 计时器id
#el // clock 代表的页面元素
// 构造函数
constructor(h = 0, m = 0, s = 0) {
this.hour = h
this.minute = m
this.second = s
// 生成页面元素
this.#el = document.createElement('div')
this.#el.className = 'clock'
document.body.appendChild(this.#el)
// 初始化渲染
this.render()
}
// 修改时间
setTime(h, m, s) {
this.hour = h
this.minute = m
this.second = s
}
// 开始计时
start() {
if (this.#timer) return
// 计时的时候进行初始渲染
this.render()
this.#timer = setInterval(() => {
// 执行计时逻辑
this.second++
// 进位判断
if (this.second >= 60) {
this.minute++
this.second = 0
if (this.minute >= 60) {
this.hour++
this.minute = 0
if (this.hour >= 24) {
this.hour = 0
}
}
}
// 渲染
this.render()
}, 1000)
}
// 停止计时
stop() {
clearInterval(this.#timer)
this.#timer = undefined
}
// 渲染函数
// 用于显示该实例对象
render() {
// 构造表内部的html代码
let html = `<span class="red">${this.hour < 10 ? '0' + this.hour : this.hour}</span>:<span class="green">${this.minute < 10 ? '0' + this.minute : this.minute}</span>:<span class="blue">${this.second < 10 ? '0' + this.second : this.second}</span>`
// 插入页面
this.#el.innerHTML = html
}
}
let clock = new Clock(23, 59, 50)
clock.start()
let clock2 = new Clock(2, 15, 30)
clock2.start()
</script>
13.this,super和继承
继承与原型:
<script>
// 什么是继承
// 从父类传承下他的属性和行为,使子类具备相同的属性和行为,同时子类可以拥有自己的属性和行为
// 父类(parent class)也叫做基类(base class),也叫超类(super class)
// 以下述三种人种为例:人类 就是 欧洲人和非洲人的父亲,非洲人和欧洲人是人类的子类
// class 类 语法下的继承
// 人类
class Human {
name
sex
sleep() {
console.log('人类在睡觉');
}
}
// 使用 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 类 语法的继承
// 汽车
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()
pk.name = '皮卡'
pk.weight = 3
console.log(pk);
// 原型 __proto__ 和 prototype 的区别
// __proto__ 实例对象中访问原型的属性
// 实例对象才能访问 __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()
</script>
静态属性和方法:
<script>
// 什么是静态属性和方法
// 静态属性和方法 指的就是一类中共有的属性和方法
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')
human.eat('火锅')
// 调用静态属性或方法,使用类名调用,而不是实例对象
console.log(Human.category);
Human.greedy()
// function 是不能使用 static 关键字声明静态属性和方法的
// function 类 只能通过给构造函数添加属性来充当静态属性和方法
function Car() { }
Car.category = '工业制品'
Car.jiaYou = function () {
console.log('汽车要加油');
}
console.log(Car.category);
Car.jiaYou()
</script>
严格模式:
<!-- js 文件顶部开启严格模式 -->
<script src="./main.js"></script>
<script>
// script 标签顶部开启严格模式
'use strict'
function fn2() {
console.log(this);
}
fn2()
// x = 12
</script>
<script>
let pi = 3.14
// with (Math) {
// console.log(floor(pi));
// }
function fn3() {
console.log(this);
}
function fn4() {
// 函数的第一行开启严格模式
'use strict'
console.log(this);
}
</script>
原型链:
<script>
// 什么是原型链?对象中的__proto__,如果存在上级,
// 那么当前对象的 __proto__ 和原型中的 __proto__ 形成的一种链式结构就是原型链
// Object 类型是所有类型的父类
class A {
name = 'A'
}
class B extends A {
sex = 'other'
}
class C extends B {
age = 17
}
let c = new C()
console.log(c);
console.log(c.name);
// 可以通过链式调用获取到不同级的原型对象
// console.log(c.__proto__);
// console.log(c.__proto__.__proto__);
// console.log(c.__proto__.__proto__.__proto__);
// console.log(c.__proto__.__proto__.__proto__.__proto__);
// console.log(c.__proto__.__proto__.__proto__.__proto__.__proto__);
function D() {
this.name = '我是D'
}
// 原型链的运行顺序:
// 当访问run函数时,浏览器会先从f对象中寻找run函数,若不存在
// 就会在父类中寻找,若父类中找不到,就会回到父类的父类中寻找直到找到为止
D.prototype.run = () => {
console.log('D在跑');
}
function E() {
this.sex = '男'
this.run = () => {
console.log('E在跑');
}
}
E.prototype = new D()
function F() {
this.age = 0
}
F.prototype = new E()
let f = new F()
console.log(f);
console.log(f.age);
console.log(f.sex);
console.log(f.name);
f.run()
// 通过上述例子,可以看出F中并不存在sex和name,但依然可以访问他们
// 因为浏览器在访问属性时的顺序如下:
// 先从自身类中寻找属性,若不存在就查找他的原型是否存在该属性,若还不存在,就查询原型的原型中是否存在,依次类推
</script>
instanceof:
<script>
// instanceof 用于检测某个对象是否是某个类型的实例
// 语法:n instanceof M
// 含义:n 是否是 M 类的实例,如果是 则表达式返回 true
// 鸟
class Bird {
name = '鸟'
constructor(name) {
this.name = name
}
fly() {
console.log('鸟在飞');
}
}
// 鸭子
class Duck extends Bird {
color
constructor(name, color) {
super(name)
this.color = color
}
fly(){
console.log(`${this.name} 在飞`);
console.log(super.name);
super.fly()
}
}
let bird = new Bird()
console.log(bird instanceof Bird);
let duck = new Duck()
console.log(duck instanceof Bird);
console.log(bird instanceof Duck);
// 结论:
// 实例对象可以是其类型和父类类型的实例
</script>
lambda表达式中的this:
<body>
<button my-data="这是我的自定义属性">点击</button>
</body>
<script>
// 结论
// 1.lambda 表达式没有 this
// 2.lambda 表达式中的 this 来自于声明函数时上下文中的 this
// 作用:
// 利用好 lambda 表达式没有 this 的特点,灵活的使用 this 获取想要的上下文中的内容
// 后面有例子
console.log(this);
let fn = () => {
'use strict'
console.log(this);
}
fn()
let obj = {
fn: () => {
console.log(this);
}
}
obj.fn()
class A {
name = '张三 '
fn() {
console.log(this);
const fn2 = () => {
console.log(this);
}
fn2()
}
}
let a = new A()
a.fn()
// 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()
</script>
super关键字:
<script>
// 鸟
class Bird {
// 名称
name = '鸟'
constructor(name) {
this.name = name
}
// 飞
fly() {
console.log('鸟在飞');
}
}
// 鸭子
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);
</script>
this关键字:
<body>
<button class="btn">click me</button>
</body>
<!-- <script>
// 参考
// this 关键字代表什么?
// 根据不同的场景,this 关键字代表东西不同,有以下几种情况
// js 执行上下文中的 this
console.log(this); // => window
console.log(this === window);
// 函数内的 this
function fn() {
// 非严格模式下 函数内 this => window
console.log(this);
}
fn()
// json 对象方法的 this
let obj = {
fn() {
console.log(this); // => 对象自己
}
}
obj.fn()
// 实例对象方法的 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 始终是自己
// 声明变量存储 A 类的 fn 方法,再次调用并观察 this
// 给变量赋值对象方法,默认情况下 this 为 undefined
const fnVar = a.fn
fnVar() // => undefined
// 给变量赋值函数值,默认情况下 this 为 undefined
const fn2 = function () {
console.log(this);
}
// 有些情况下,回调函数中的 this 可能被其他函数赋值,所以 this 不是 undefined
// 此处 事件回调函数被 addEventListener 赋值了其中的 this
// 所以这里的 this 指的是绑定事件的那个 dom 对象
document.querySelector('.btn').addEventListener('click', function () {
console.log(this);
})
// 自定义函数也可以修改回调函数内的 this 指代
function custom(callback) {
// call apply bind
callback.call('hello world')
}
custom(function () { console.log(this); })
// 总结:
// 1.js 执行上下文 => window
// 2.函数内 this => window(函数 => window)
// 3.方法内的 this 指向调用方法的对象 (方法 => 实例)
// 4.this 可能被其他函数赋值 例如 addEventListener 这种时候 this 既不是 window 也不是 undefined
</script> -->
<script>
'use strict'
// 结论:
// 1.js 执行上下文 => window
// 2.只要是函数 this 就是 undefined (函数 => undefined)
// 3.方法内的 this 指向调用方法的对象 (方法 => 实例)
// 4.this 可能被其他函数赋值 例如 addEventListener 这种时候 this 既不是 window 也不是 undefined
// js 执行上下文中的 this
console.log(this); // => window
console.log(this === window);
// 函数内的 this
function fn() {
// 严格模式下 函数内 this => undefined
console.log(this);
}
fn()
// json 对象方法的 this
let obj = {
fn() {
console.log(this); // => 对象自己
}
}
obj.fn()
// 实例对象方法的 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 始终是自己
// 声明变量存储 A 类的 fn 方法,再次调用并观察 this
// 给变量赋值对象方法,默认情况下 this 为 undefined
const fnVar = a.fn
fnVar() // => undefined
// 给变量赋值函数值,默认情况下 this 为 undefined
const fn2 = function () {
console.log(this);
}
// 有些情况下,回调函数中的 this 可能被其他函数赋值,所以 this 不是 undefined
// 此处 事件回调函数被 addEventListener 赋值了其中的 this
// 所以这里的 this 指的是绑定事件的那个 dom 对象
document.querySelector('.btn').addEventListener('click', function () {
console.log(this);
})
// 自定义函数也可以修改回调函数内的 this 指代
function custom(callback) {
// call apply bind
callback.call('hello world')
}
custom(function () { console.log(this); })
</script>
14.Object类方法和访问器:
delete关键字:
<script>
// 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);
</script>
Object常用静态对象:
<script>
// Object 的常用静态方法
// Object.assign : 拓展对象
// 将一个对象的属性复制到另一个对象中,且返回一个新对象
let x = {
a: 1,
b: 2
}
let y = {
c: 3,
d: 4
}
// 第一个参数:要拓展属性的对象
// 第二个参数:拓展的属性源对象
// Object.assign(x, y) 这句话的含义就是,将y中的属性拷贝到x中,且返回x对象
let r = Object.assign(x, y)
console.log(r);
// Object.assign 该函数常用于浅拷贝对象
// 拷贝对象
let obj = { name: '张三' }
r = Object.assign({}, obj)
console.log(r);
// 拷贝数组
let arr = [1, 2, 3, 4]
r = [].concat(arr)
console.log(r);
// 浅拷贝例子
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);
// 参考地址:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
// Object.defineProperty : 定义属性
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 方法
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');
</script>
访问器getter和setter:
<script>
// 访问器是什么?访问属性用的函数,访问有两重含义,读值和赋值
// 作用:可以控制读取属性的结果,可以控制赋值属性的内容(控制访问)
// 具体的作用
// getter:
// 1. 格式化数据显示
// 2. 简化路径访问
// setter:
// 1. 验证赋值参数的合法性
// 对象中声明访问器
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 = '其他'
</script>
包装类:
<script>
// 什么是包装类?
// 将我们熟知的基本的数据类型用类来进行包装,这种类就是包装类
// 作用,多用于类型转换
// 包装类有以下几种
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))
</script>