简介
- JavaScript 是脚本语言
- JavaScript 是一种轻量级的编程语言。
- JavaScript 是可插入 HTML 页面的编程代码。
- JavaScript 插入 HTML 页面后,可由所有的现代浏览器执行。
- 浏览器会在读取代码时,逐行地执行脚本代码。而对于传统编程来说,会在执行前对所有代码进行编译。
-
ECMASCRIPT: 定义了 javascript 的语法规范,描述了语言的基本语法和数据类型
-
BOM (Browser object Model): 浏览器对象模型
- 有一套成熟的可以操作浏览器的 API,通过 BOM 可以操作浏览器。比如:弹出框、 浏览器跳转、获取分辨率等
-
DOM (Document object Model): 文档对象模型
- 有一套成熟的可以操作页面元素的 API,通过 DOM 可以操作页面中的元素。
总结: JS 就是通过固定的语法去操作浏览器和标签结构来实现网页上的各种效果
代码的书写位置
-
行内式
- 写在标签上的 js 代码需要依靠事件(行为)来触发
- 在标签中填写 onclick 事件调用函数时,不是 onclick=函数名, 而是 onclick=函数名()
//写在a标签的href属性上 <a href="javascript:alert('我是一个弹出层');">点击一下试试</a> //写在其他元素上 <div onclick="alert('我是一个弹出层')">点一下试试看</div> -
内嵌式
- 内嵌式的 js 代码会在页面打开的时候直接触发
- HTML 中的 Javascript 脚本代码必须位于
<script>与</script>标签之间 - script 标签可以放在 head 里面也可以放在 body 里面
<script type="text/javascript">alert('我是一个弹出层')</script> -
外链式 外链式 js 代码只要引入了 html 页面,就会在页面打开的时候直接触发 新建一个.js 后缀的文件,在文件内书写 js 代码,把写好的 js 文件引入 html 页面
//我是index.js 文件 alert("我是一个弹出层")<!-- 我是一个html文件--> <!--通过script标签的src 属性,把写好的js文件引入页面--> <script src="index.js"></script> <!-- 一个页面可以引入多个js文件 --> <script src="index1.js"></script> <script src="index2.js"></script>
输出
JavaScript 没有任何打印或者输出的函数。
显示数据 JavaScript 可以通过不同的方式来输出数据:
-
使用 window.alert() 弹出警告框。
-
使用 document.write() 方法将内容写到 HTML 文档中。如果在文档已完成加载后执行 document.write,整个 HTML 页面将被覆盖。
<body> <h1>我的第一个 Web 页面</h1> <p>我的第一个段落。</p> <button onclick="myFunction(event)">点我</button> <script> function myFunction(e) { //console.log(event) 获取事件对象方式2 document.write(Date()) } </script> </body> -
使用 innerHTML 写入到 HTML 元素。
-
使用 console.log() 写入到浏览器的控制台。
注释
<script>
// alert("hello world")
/*
console.log("hello world")
console.log("hello world")
*/
/* 建议不加分号,两句写在同一行要加分号 */
</script>
变量
-
变量指的是在程序中保存数据的一个容器
-
变量是计算机内存中存储数据的标识符,根据变量名称可以获取到内存中存储的数据
-
如果把值赋给尚未声明的变量,该变量将被自动作为 window 的一个属性(可用 delete 删除)。
-
语法:
var 变量名=值//定义一个变量 var num //给一个变量赋值 num = 100 //定义一个变量的同时给其赋值 var num2 = 200 //在一条语句中声明很多变量 var lastname = "Doe", age = 30, job = "carpenter" //声明也可横跨多行,一条语句中声明的多个变量不可以同时赋同一个值 var x, y, z = 1 //x,y 为undefined,z为 1。
注意:
- 一个变量名只能存储一个值
- 当再次给一个变量赋值的时候,前面一次的值就没有了
- 变量名称区分大小写(JS 严格区分大小写)
命名规则
规则:必须遵守的,不遵守就是错
- 一个变量名称可以由数字、字母、英文下划线(_) 、美元符号($)组成
- 严格区分大小写
- 不能由数字开头
- 不能是保留字或者关键字
- 不要出现空格
规范:建议遵守的(开发者默认),不遵守不会报错
- 变量名尽量有意义(语义化)
- 遵循驼峰命名规则,由多个单词组成的时候,从第二个单词开始首字母大写
- 不要使用中文
数据类型
- 是指我们存储在内存中的数据的类型
- 我们通常分为两大类基本数据类型和复杂数据类型
基本数据类型
- 数值类型(number)
- 一切数字都是数值类型(包括二进制,十进制,十六进制等)
- NaN (not a number),一个非数字
var year = 2e3 // 2*10^3 var a = 0xff // 1-9 ,a,b,c,d,e,f 十六进制 var b = 077 //八进制 var c = 0b11 //二进制 - 字符串类型(string)
- 被引号包裹的所有内容(可以是单引号也可以是双引号)
- 布尔类型(boolean)
- 只有两个(true 或者 false)
- null 类型(null)
var obj = null obj = { name: "A" } - undefined:undefined
var app //声明了但未赋值 console.log(app) - Symbol
引用数据类型(对象类型)
- 对象(Object)
- 数组(Array)
- 函数(Function)
- 正则(RegExp)
- 日期(Date)
存储的区别
存储空间分成两种栈和堆
- 栈:主要存储基本数据类型的内容
- 堆:主要存储复杂数据类型的内容
-
基本数据类型在内存中的存储情况
- var num = 100,在内存中的存储情况
- 直接在栈空间内有存储一个数据
-
复杂数据类型在内存中的存储情况
var obj = { name: "Jack", age: 18, gender: "男" }- 在堆里面开辟一个存储空间
- 把数据存储到存储空间内
- 把存储空间的地址赋值给栈里面的变量
var obj = { name: "小王", age: 100 } var obj2 = obj console.log(obj === obj2) //true obj2.name = "小李" console.log(obj, obj2) //{name: '小李', age: 100} {name: '小李', age: 100} var obj3 = { name: "小王" } var obj4 = { name: "小王" } console.log(obj3 == obj4) //false
判断数据类型
typeof
-
使用 typeof 关键字来进行判断,结果是字符串
typeof undefined // undefined typeof null // object null === undefined // false null == undefined // true -
如果对象是 Array 或 Date ,我们就无法通过 typeof 来判断他们的类型,因为都是 返回 object。
//第一种使用方式 var n = 100 console.log(typeof n) // 第二种使用方式 console.log(typeof (n + 100))
constructor 属性
constructor 属性返回变量的构造函数。
"John".constructor // 返回函数 String() { [native code] }
(3.14).constructor // 返回函数 Number() { [native code] }
false.constructor // 返回函数 Boolean() { [native code] }
[1,2,3,4].constructor // 返回函数 Array() { [native code] }
{name:'John', age:34}.constructor // 返回函数 Object() { [native code] }
new Date().constructor // 返回函数 Date() { [native code] }
function () {}.constructor // 返回函数 Function(){ [native code] }
可以使用 constructor 属性来查看对象是否为数组、日期
类型转换
-
其他数据类型转成数值
- Number(变量)
- 可以把一个变量强制转换成数值类型
- 可以转换小数,会保留小数
- 可以转换布尔值
- 遇到不可转换的都会返回 NaN
console.log(Number(null)) //0 console.log(Number("")) //0 console.log(Number(undefined)) //NaN - parseInt(变量)
- 从第一位开始检查,是数字就转换,直到一个不是数字的内容
- 开头就不是数字,那么直接返回 NaN
- 不认识小数点,只能保留整数
- parseFloat(变量)
- 从第一位开始检查,是数字就转换,直到一个不是数字的内容
- 开头就不是数字,那么直接返回 NaN
- 认识小数点
- 除了加法以外的数学运算(隐式类型转换)
- 运算符两边都是可运算数字才行
- 如果运算符任何一边不是一个可运算数字,那么就会返回 NaN
- 加法不可以用
//-0 /1 *1 var a = "100" console.log(a - 0)
- Number(变量)
-
其他数据类型转成字符串
- 变量.toString() 有一些数据类型不能使用 toString() 方法,比如 undefined 和 null
- String(变量) 所有数据类型都可以
- 使用加法运算
在 JS 里面,+有两个含义
- 字符串拼接:只要+任意一边是字符串,就会进行字符串拼接
- 加法运算:只有+两边都是数字的时候,才会进行数学运算
-
其他数据类型转成布尔
- Boolean(变量) 在 js 中,只有""、0、null、undefined、NaN, 这些是 false,其余都是 true
- !!a 转换成布尔值
运算符
就是在代码里面进行运算的时候使用的符号
数学运算符
- + 只有符号两边都是数字的时候才会进行加法运算 只要符号任意一边是字符串类型,就会进行字符串拼接
- - 会执行减法运算 会自动把两边都转换成数字进行运算
- * 会执行乘法运算 会自动把两边都转换成数字进行运算
- / 会执行除法运算 会自动把两边都转换成数字进行运算
- % 会执行取余运算 会自动把两边都转换成数字进行运算
console.log(1 + "2" * 3 + 4) //11
console.log("1" + "2" * 3 + 4) //164
### 赋值运算符
- = 就是把=右边的赋值给等号左边的变量名
- += a+=10 等价于 a=a+10
- -= a-=10 等价于 a=a-10
- *= a*=10 等价于 a=a*10
- %= a%=10 等价于 a=a%10
### 比较运算符
- ==
比较符号两边的值是否相等,不管数据类型
1 == '1' 两个的值是一样的,所以得到 true0 == false //true;存在自动转换。 "" == 0 //true;存在自动转换。 "" == false //true;存在自动转换。 false == undefined //false undefined == 0 //false null == 0 //false null == undefined //true - === 比较符号两边的值和数据类型是否都相等 1 === '1' 两个值虽然一样,但是因为数据类型不一样,所以得到 false
- != 比较符号两边的值是否不等 1 != '1' 因为两边的值是相等的,所以比较他们不等的时候得到 false
- !== 比较符号两边的数据类型和值是否不等 1 !== '1' 因为两边的数据类型确实不一样,所以得到 true
- > 比较左边的值是否大于右边的值
- < 比较左边的值是否小于右边的值
- >= 比较左边的值是否大于或等于右边的值
- <= 比较左边的值是否小于或等于右边的值
逻辑运算符
- &&
- 进行且的运算
- 符号左边必须为 true 并且右边也是 true,才会返回 true
- 只要有一边不是 true,那么就会返回 false
true && true 结果是true true && false结果是false false && true结果是false false && false结果是false
- ||
- 进行或的运算
- 符号的左边为 true 或者右边为 true,都会返回 true
- 只有两边都是 false 的时候才会返回 false
true || true 结果是true true || false 结果是true false || true 结果是true false || false 结果是false
- !
- 进行取反运算
- 本身是 true 的,会变成 false
- 本身是 false 的,会变成 true
特殊:
&&||短路用法
在&&的运算中,代码从左往右执行,如果都为真,则返回最后一个值,当遇到假值的时候,就会发生短路,并且返回这个假值,代码不再往下执行。
在||的运算中,代码从左往右执行,如果都为假,则返回最后一个假值,当遇到真值的时候,就会发生短路,并且返回这个真值,代码不再往下执行。
var x = "1111"
console.log(!!x)
var y
console.log(y && y.toString())
var z = 10
document.write(z || "这个家伙很赖,什么也没有留下")
let obj = {}
obj.name ||= "name"
?. 运算符和 ?? 运算符
-
链判断运算符(?. 运算符)
-
短路机制
a?.[++x] // 等同于 a === null/undefined ? undefined : a[++x]
-
右侧不得为十进制数值 为了保证兼容以前的代码,允许 foo ?. 3 : 0 被解析成 foo ? .3 : 0,因此规定如果 ?. 后面紧跟一个十进制数字,那么 ?. 不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。
-
-
Null 判断运算符(??)
当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。
const animationDuration = settings?.animationDuration ?? 300 //用||的话false、""、0也会使用默认值
自增自减运算符
- ++
- 进行自增运算
- 分成两种,前置++和后置++
- 前置++,会先把值自动+1,在返回
var a = 10 console.log(++a) // 会返回 11,并且把a的值变成 11 - 后置++,会先把值返回,在自动+1
var a = 10 console.log(a++) //会返回10,然后把a的值变成11
- --
- 进行自减运算
- 分成两种,前置--和后置--
- 和++运算符道理一样
三元运算符
-
三元运算,就是用两个符号组成一个语句
-
语法:条件?条件为 true 的时候执行:条件为 false 的时候执行
var age = 18 age >= 18 ? alert("已经成年") : alert("没有成年") var sum = 90 var youhuisum = sum > 200 ? sum - 10 : sum > 100 ? sum - 5 : sum
分支结构
逻辑分支就是根据我们设定好的条件来决定要不要执行某些代码
IF 条件分支结构
if 语句
- 通过一个
if语句来决定代码是否执行 - 语法: if (条件) { 要执行的代码 }
- 通过()里面的条件是否成立来决定{}里面的代码是否执行
//条件为 true的时候执行 {}里面的代码 if (true) { alert("因为条件是true,我会执行") } //条件为false 的时候不执行{}里面的代码 if (false) { alert("因为条件是false, 我不会执行") }
if else 语句
- 通过
if条件来决定执行哪一个{}里面的代码 - 语法: if (条件) {条件为 true 的时候执行} else {条件为 false 的时候执行}
- 两个{}内的代码一定有一个会执行
// 条件为 true的时候, 会执行if后面的 {} if (true) { alert("因为条件是true, 我会执行") } else { alert("因为条件是true,我不会执行") } //条件为false 的时候,会执行else 后面的{} if (false) { alert("因为条件为false,我不会执行") } else { alert("因为条件为false,我会执行") }
if else if ...语句
- 可以通过
if和else if来设置多个条件进行判断 - 语法: if (条件 1) {条件 1 为 true 的时候执行} else if (条件 2) {条件 2 为 true 的时候执行}
- 会从头开始依次判断条件
- 如果第一个条件为 true 了,那么就会执行后面的{}里面的内容
- 如果第一个条件为 false,那么就会判断第二个条件,依次类推
- 多个{},只会有一个被执行,一旦有一个条件为 true 了,后面的就不在判断了
// 第一个条件为 true,第二个条件为 false,最终会打印“我是代码段1” if (true) { alert("我是代码段1") } else if (false) { alert("我是代码段2") } //第一个条件为 true,第二个条件为 true,最终会打印 “我是代码段1” //因为只要前面有一个条件满足了,就不会继续判断了 if (true) { alert("我是代码段1") } else if (true) { alert("我是代码段2") }
if else if ... else 语句
- 和之前的
if else if ...基本一致,只不过是在所有条件都不满足的时候,执行最后else后面的{}//第一个条件为false,第二个条件为false, 最终会打印“我是代码段3” //只有前面所有的条件都不满足的时候会执行else后面的{} 里面的代码 //只要前面有一个条件满足了,那么后面的就都不会执行了 if (false) { alert("我是代码段1") } else if (false) { alert("我是代码段2") } else { alert("我是代码段3") }
SWITCH 条件分支结构
- 也是条件判断语句的一种,严格匹配(值和类型相同)
- 是对于某一个变量的判断
- 语法:
switch (要判断的变量) { case 情况1: 情况1要执行的代码 break case 情况2: 情况2要执行的代码 break case 情况3: 情况3要执行的代码 break default: 上述情况都不满足的时候执行的代码 }- 要判断某一个变量等于某一个值的时候使用
//成绩的案例改写switch var score = 85 var flagScore = parseInt(score / 10) switch (flagScore) { case 10: case 9: console.log("A") break case 8: console.log("B") break case 7: console.log("C") break case 6: console.log("D") break default: console.log("E") break }
循环结构
- 循环结构,就是根据某些给出的条件,重复的执行同一段代码
- 循环必须要有某些固定的内容组成
- 初始化
- 条件判断
- 要执行的代码
- 自身改变
WHILE 循环
- 当条件满足时就执行代码,一旦不满足了就不执行了
- 语法 while (条件) {满足条件就执行}
- 设定一个边界值,不然就一直循环下去了
// 1.初始化条件 var num = 0 // 2.条件判断 while (num < 10) { // 3.要执行的代码 console.log("当前的num的值是" + num) //4.自身改变 num = num + 1 }
DO WHILE 循环
-
while 会先进行条件判断,满足就执行,不满足直接就不执行了,但是 do while 循环是,先不管条件,先执行一回,然后在开始进行条件判断
-
语法: do {要执行的代码} while (条件)
do { var res = prompt("请输入你的名字") // console.log(res) if (res) { document.write(res) } } while (!res)
FOR 循环
- 语法:for(初始条件;结束条件;条件改变){ 要执行的代码 }
// 把初始化,条件判断,自身改变,写在了一起 for (var i = 1; i <= 10; i++) { //这里写的是要执行的代码 console.log(i) } //控制台会依次输出1 ~ 10
for...in
一般用于对象的遍历
var obj = { a: 1, b: 2, c: 3 }
for (var i in obj) {
console.log("键名:", i) //数组是索引
console.log("键值:", obj[i])
}
for...of
for...of 语句在可迭代(拥有 Iterator 接口)对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
语法
- variable: 在每次迭代中,将不同属性的值分配给变量。
- iterable: 被迭代枚举其属性的对象。
for (variable of iterable) {
//statements
}
遍历数组
var arr = [
{ name: "nick", age: 18 },
{ name: "freddy", age: 24 },
{ name: "mike", age: 26 },
{ name: "james", age: 34 }
]
for (var item of arr) {
console.log(item.name, item.age)
}
// nick 18
// freddy 24
// mike 26
// james 34
遍历 String
let iterable = "boo"
for (let value of iterable) {
console.log(value)
}
// "b"
// "o"
// "o"
遍历 Map
let iterable = new Map([
["a", 1],
["b", 2],
["c", 3]
])
for (let entry of iterable) {
console.log(entry)
}
// ["a", 1]
// ["b", 2]
// ["c", 3]
for (let [key, value] of iterable) {
console.log(value)
}
// 1
// 2
// 3
遍历 Set
let iterable = new Set([1, 1, 2, 2, 3, 3])
for (let value of iterable) {
console.log(value)
}
// 1
// 2
// 3
遍历 arguments
function fn() {
for (let argument of arguments) {
console.log(argument)
}
}
fn(1, 2, 3)
// 1
// 2
// 3
break 终止循环
- 在循环没有进行完毕的时候,因为我设置的条件满足,提前终止循环
- 要终止循环,就可以直接使用 break 关键字
for (var i = 1; i <= 5; i++) { //每循环一次,吃一个包子 console.log("我吃了一个包子") //当i的值为3的时候,条件为true,执行{}里面的代码终止循环 //循环就不会继续向下执行了,也就没有4和5了 if (i === 3) { break } }
continue 结束本次循环
- 在循环中,把循环的本次跳过去,继续执行后续的循环
- 跳过本次循环,就可以使用 continue 关键字
for (var i = 1; i <= 5; i++) {
//当i的值为3的时候,执行{}里面的代码
//{}里面有 continue,那么本次循环后面的代码就都不执行了
//自动算作 i为3的这一次结束了,去继续执行i=4的那次循环了
if (i === 3) {
console.log("这个是第三个包子,掉地下了,我不吃了")
continue
}
console.log("我吃了一个包子")
}
标签
如需标记 JavaScript 语句,请在语句之前加上冒号:
label: statements
语法:
-
break labelname;
-
continue labelname;
continue 语句(带有或不带标签引用)只能用在循环中。 break 语句(不带标签引用),只能用在循环或 switch 中。 通过标签引用,break 语句可用于跳出任何 JavaScript 代码块:
let cars = ["BMW", "Volvo", "Saab", "Ford"]
list: {
document.write(cars[0] + "<br>")
document.write(cars[1] + "<br>")
document.write(cars[2] + "<br>")
break list
document.write(cars[3] + "<br>")
document.write(cars[4] + "<br>")
document.write(cars[5] + "<br>")
}
outerloop: for (var i = 0; i < 10; i++) {
innerloop: for (var j = 0; j < 10; j++) {
if (j > 3) {
break
}
if (i == 2) {
break innerloop
}
if (i == 4) {
break outerloop
}
document.write("i=" + i + " j=" + j + "")
}
}
函数
概念
-
函数就是包裹在花括号中的代码块,前面使用了关键词 function
-
当调用该函数时,会执行函数内的代码。
-
可以在某事件发生时直接调用函数,也可在任何位置进行调用。
//函数,这个{}就是那个“盒子” function fn() { for (var i = 0; i < 10; i++) { console.log(i) } }
函数定义
- 函数是一个复杂数据类型(Function)
- 定义阶段就是我们把代码放在盒子里面
声明式
-
使用 function 这个关键字来声明一个函数
-
语法:
function fn() { //一段代码 } // function: 声明函数的关键字,表示接下来是一个函数了 // fn:函数的名字,我们自己定义的(遵循变量名的命名规则和命名规范) // (): 必须写,是用来放参数的位置 // {}: 就是我们用来放一段代码的位置
赋值式
- 首先使用 var 定义一个变量,把一个函数当作值直接赋值给这个变量就可以了
- 语法:
var fn = function () { //一段代码 } //不需要在function 后面书写函数的名字了,因为在前面已经有了
函数调用
两种定义函数的方式不同,但是调用函数的方式都是一样的
- 函数调用就是直接写函数名()就可以了
//声明式函数
function fn() {
console.log("我是fn函数")
}
//调用函数
fn()
//赋值式函数
var fn2 = function () {
console.log("我是fn2函数")
}
//调用函数
fn2()
注意:定义完一个函数以后,如果没有函数调用,那么写在{}里面的代码没有意义,只有调用以后才会执行
调用上的区别
-
虽然两种定义方式的调用都是一样的,但是还是有一些区别的
- 声明式函数:调用可以在定义之前或者定义之后
//可以调用 fn() //声明式函数 function fn() { console.log("我是fn函数") } //可以调用 fn()- 赋值式函数:调用只能在定义之后
//会报错 fn() //赋值式函数 var fn = function () { console.log("我是fn函数") } //可以调用 fn()
函数参数
在调用函数时,可以向其传递值,这些值被称为参数。
- ()就是用来放参数的位置
- 参数分为两种行参和实参
//声明式
function fn(行参写在这里) {
//一段代码
}
fn(实参写在这里)
//赋值式函数
var fn = function (行参写在这里) {
//一段代码
}
fn(实参写在这里)
作用
- 行参
-
就是在函数内部可以使用的变量,在函数外部不能使用
-
每写一个单词,就相当于在函数内部定义了一个可以使用的变量(遵循变量名的命名规则和命名规范)
-
多个单词之间以,分隔
//书写一个参数 function fn(num) { //在函数内部就可以使用num这个变量 } var fn1 = function (num) { //在函数内部就可以使用num这个变量 } //书写两个参数 function fun(num1,num2) { //在函数内部就可以使用num1和num2这两个变量 } var fun1 = function (num1,num2) { //在函数内部就可以使用num1和num2这两个变量 } -
如果只有行参的话,那么在函数内部使用的这个变量是没有值的,也就是 undefined
-
行参的值是在函数调用的时候由实参决定的
- 实参
-
在函数调用的时候给行参赋值的
-
也就是说,在调用的时候是给一个实际的内容的
function fn(num) { //函数内部可以使用num } //这个函数的本次调用,书写的实参是100 // 那么本次调用的时候函数内部的num就是100 fn(100) //这个函数的本次调用,书写的实参是200 //那么本次调用的时候函数内部的num就是200 fn(200) -
函数内部的行参的值,由函数调用的时候传递的实参决定
-
多个参数的时候,是按照顺序一一对应的
function fn(num1, num2) { //函数内部可以使用num1 和num2 } //函数本次调用的时候,书写的参数是100和200 //那么本次调用的时候,函数内部的num1就是100,num2就是200 fn(100, 200)
参数个数的关系
-
行参比实参少
- 因为是按照顺序一一对应的
- 行参少就会拿不到实参给的值,所以在函数内部就没有办法用到这个值
function fn(num1, num2) { //函数内部可以使用num1和num2 } //本次调用的时候,传递了两个实参,100 200和300 //100 对应了num1,200 对应了num2,300 没有对应的变量 //所以在函数内部就没有办法依靠变量来使用300这个值 fn(100, 200, 300) -
行参比实参多
- 因为是按照顺序一一对应的
- 所以多出来的行参就是没有值的,就是 undefined
function fn(num1, num2, num3) { //函数内部可以使用num1 num2和num3 } //本次调用的时候,传递了两个实参,100和200 //就分别对应了num1和num2 //而num3没有实参和其对应,那么num3的值就是undefined fn(100, 200)
返回值
-
return 返回的意思,其实就是给函数一个返回值和终断函数
-
函数调用本身也是一个表达式,表达式就应该有一个值出现
function fn() { //执行代码 } // fn()也是一个表达式,这个表达式就没有结果出现 console.log(fn()) // undefined -
return 关键字就是可以给函数执行完毕一个结果
function fn(x, y, z) { var result = x + y + z return result } //此时,fn() 这个表达式执行完毕之后就有结果出现了 console.log(fn(1, 2, 3)) //6 -
我们可以在函数内部使用 return 关键字把任何内容当作这个函数运行后的结果
注意:
- 是否要 return 看需求
- return 后面的函数代码无法执行了
- 仅仅希望退出函数时,可以不用返回值
预解析
- 预解析其实就是聊聊 js 代码的编译和执行
- js 是一个解释型语言,就是在代码执行之前,先对代码进行通读和解释,然后再执行代码
- 也就是说,我们的 js 代码在运行的时候,会经历两个环节解释代码和执行代码
解释代码
-
函数及变量(var)的声明都将被提升到作用域的最顶部。
-
var 的创建和初始化被提升,赋值不会被提升。
-
let 的创建被提升,初始化和赋值不会被提升。
-
function 的创建、初始化和赋值均会被提升。
-
提升不能跨 script 标签,访问可以跨
a = 5
console.log(a) // 5
var a
赋值不会被提升
console.log(a) //undefined
var a = 5
当变量在函数内部使用时,变量仅被提升到函数的顶部
var a = 4
function greet() {
b = "hello"
console.log(b)
var b
}
greet() //hello
console.log(b) //Uncaught ReferenceError: b is not defined
可以在声明之前调用函数
greet()
function greet() {
console.log("Hi, there.")
}
//Hi, there
变量声明更加优先,其次是函数声明
console.log(a) // 函数
var a = 1
console.log(a) //变量 1
function a() {}
console.log(a) //变量 1
var c = 1
function d(c) {
console.log(c)
var c = 2
}
d(3) // 3
console.log(c) // 1
//解释:
var c // 全局
function d(c) {
//c形参可以看作: var c = 实参
var c // 形参
var c // 下面的c = 2的变量
c = 3 // 实参
console.log(c) // 3
c = 2
}
c = 1 // 全局
d(3)
console.log(c) //1
作用域
- 什么是作用域,就是一个变量可以生效的范围
- 变量不是在所有地方都可以使用的,而这个变量的使用范围就是作用域
全局作用域
-
全局作用域是最大的作用域
-
变量在函数外定义,即为全局变量。
-
在全局作用域中定义的变量可以在任何地方使用
-
页面打开的时候,浏览器会自动给我们生成一个全局作用域 window
-
这个作用域会一直存在,直到页面关闭就销毁了
//下面两个变量都是存在在全局作用域下面的,都是可以在任意地方使用的 var num = 100 var num2 = 200
局部作用域
- 局部作用域就是在全局作用域下面有开辟出来的一个相对小一些的作用域
- 在局部作用域中定义的变量只能在这个局部作用域内部使用
- 在 JS 中只有函数、let 和 const 能生成一个局部作用域,别的都不行
- 每一个函数,都是一个局部作用域
//这个num是一个全局作用域下的变量在任何地方都可以使用 var num = 100 function fn() { //下面这个变量就是一个fn局部作用域内部的变量 //只能在fn函数内部使用 var num2 = 200 } fn()
访问规则
- 当我想获取一个变量的值的时候,我们管这个行为叫做访问
- 获取变量的规则:
- 首先,在自己的作用域内部查找,如果有,就直接拿来使用
- 如果没有,就去上一级作用域查找,如果有,就拿来使用
- 如果没有,就继续去上一级作用域查找,依次类推
- 如果一直到全局作用域都没有这个变量,那么就会直接报错(该变量 is not defined)
var num = 100
function fn() {
var num2 = 200
function fun() {
var num3 = 300
console.log(num3) //自己作用域内有,拿过来用
console.log(num2) //自己作用域内没有,就去上一级,就是fn的作用域里面找,发现有,拿过来用
console.log(num) //自己这没有,去上一级fn那里也没有,再上一级到全局作用域,发现有,直接用
console.log(a) //自己没有,一级一级找上去到全局都没有,就会报错
}
fun()
}
fn()
- 变量的访问规则也叫做作用域的查找机制
- 作用域的查找机制只能是向上找,不能向下找
function fn() {
var num = 100
}
fn()
console.log(num) // 发现自己作用域没有,自己就是全局作用域,没有上一级了,直接报错
赋值规则
- 当你想给一个变量赋值的时候,那么就先要找到这个变量,再给它赋值
- 变量赋值规则:
- 先在自己作用域内部查找,有就直接赋值
- 没有就去上一级作用域内部查找,有就直接赋值
- 还没有再去上一级作用域查找,有就直接赋值
- 如果一直找到全局作用域都没有,那么就把这个变量定义为全局对象的属性,再给它赋值
function fn() {
num = 100
}
fn()
//fn调用以后,要给num赋值
//查看自己的作用域内部没有num变量
//就会向上一级查找
//上一级就是全局作用域,发现依旧没有
//那么就会把num定义为window的属性,并为其赋值
//所以fn()以后,全局就有了一个变量叫做num并且值是100
console.log(num) // 100
对象
- 对象是一个复杂数据类型.
- 对象是拥有属性和方法的数据。
var obj = { num: 100, str: "hello world", boo: true } - 对象就是一个键值对的集合
- {}里面的每一个键都是一个成员
- 对象中的键值对通常称为对象属性。
创建对象
-
字面量的方式创建一个对象
//创建一个空对象 var obj = {} //向对象中添加成员 obj.name = "Jack" obj.age = 18 -
内置构造函数的方式创建对象
- object 是 js 内置给我们的构造函数,用于创建一个对象使用的
//创建一个空对象 var obj = new Object() //向对象中添加成员 obj.name = "Rose" obj.age = 20
方式 1
//增
var obj = {}
console.log(obj)
obj.name = "kerwin"
obj.age = 100
obj.location = "dalian"
console.log(obj)
//查
document.write("姓名是" + obj.name)
//改
obj.age = 200
console.log(obj)
//删
delete obj.name
console.log(obj)
方式 2
// 增
var obj2 = {}
var name = "aaa"
obj2[name] = "tiechui" //obj2["aaa"]="tiechui"
//查
console.log(obj2[name])
//改
obj2[name] = "tiechui2"
//删
delete obj2[name]
console.log(obj2)
区别:
var myobj = {
"a+b": "111111",
"#aa": "222222"
}
// console.log(myobj."a+b")报错
console.log(myobj["a+b"])
数组
- 复杂数据类型
- 数组是一个数据的集合
- 数组就是一个[ ]
- 在[ ]里面存储着各种各样的数据,按照顺序依次排好 [1,2,3,'hello',true,false] 这个东西就是一个数组,存储着一些数据的集合
创建数组
字面量创建数组
直接使用[]的方式创建一个数组
//创建一个空数组
var arr1 = []
//创建一个有内容的数组
var arr2 = [1, [2, 3], { name: "" }]
内置构造函数创建数组
使用 js 的内置构造函数 Array 创建一个数组
//创建一个空数组
var arr1 = new Array()
//创建一个长度为10的数组
var arr2 = new Array(10)
//创建一个有内容的数组
var arr3 = new Array(1, 2, 3)
length 可读可写
var arr1 = [1, 2, 3, 4, 8]
console.log(arr1.length)
arr1.length = 3
console.log(arr1)
arr1.length = 0
//清空数组。
Array.from() Array.from()方法对一个类似数组或可迭代对象创建一个新的,返回浅拷贝的数组实例。
| 参数 | 描述 |
|---|---|
| object | 必需。需转换为数组的对象。 |
| mapFunction | 可选。对数组的每个项目调用的 map 函数。 |
| thisValue | 可选。执行 mapFunction 时用作 this 的值。 |
let mySet = new Set([1, 3, 4, 2, 5])
let arr = Array.from(
mySet,
function (currentValue, index) {
console.log(this, index)
return currentValue + 1
},
{}
)
console.log(arr)
索引
var arr2 = ["kerwin", "xiaoming", "tiechui"]
console.log(arr2[0], arr2[1], arr2[3])
arr2[0] = "kevin"
arr2[3] = "gangdaner"
arr2[5] = "aaa"
console.log(arr2)
去重 方法 1
var arr = [1, 2, 3, 4, 3, 5, 6, 2, 1]
var arr2 = []
for (var i = 0; i < arr.length; i++) {
if (arr2.indexOf(arr[i]) === -1) {
arr2.push(arr[i])
}
}
console.log(arr, arr2)
方法 2
var obj = {}
for (var i = 0; i < arr.length; i++) {
obj[arr[i]] = "随便"
}
console.log(obj)
var arr2 = []
for (var i in obj) {
arr2.push(Number(i))
}
console.log(arr2)
方法 3
var set1 = new Set(arr)
console.log(set1)
var arr1 = Array.from(set1)
console.log(arr1)
常用方法
- 数组是一个复杂数据类型,我们在操作它的时候就不能再像基本数据类型一样操作了
- 比如我们想改变一个数组
//创建一个数组 var arr=[1,2,3] //我们想把数组变成只有1和2 arr = [1,2]- 这样肯定是不合理,因为这样不是在改变之前的数组
- 相当于新弄了一个数组给到 arr 这个变量了
- 相当于把 arr 里面存储的地址给换了,也就是把存储空间换掉了,而不是在之前的空间里面修改
- 所以我们就需要借助一些方法,在不改变存储空间的情况下,把存储空间里面的数据改变了
影响原数组的方法
unshift
可向数组的开头添加一个或更多元素,并返回新的长度。
var res = arr.unshift("name", "age")
console.log(arr)
console.log("返回值", res)
shift
删除原数组第一项,并返回删除元素的值;如果数组为空则返回 undefined
var res = arr.shift()
console.log(arr)
console.log("返回值", res)
push
向数组的尾部添加若干元素,并返回数组的新长度;
var arr = [1, 2, 3]
var res = arr.push("name", "age")
console.log(arr)
console.log("返回值", res)
pop
从数组的尾部删除 1 个元素并返回被删除的元素;空数组返回 undefined
var res = arr.pop()
console.log(arr)
console.log("返回值", res)
splice
splice(index,len,...item) 其中,index:数组开始下标 len: 替换/删除的长度 item:替换的值,删除操作的话 item 为空
splice 作用:删除元素/插入元素/替换元素,该方法会改变原始数组
返回删除的元素组成的数组
//splice删除
var arr = ["kerwin", "xiaoming", "tiechui"]
var res = arr.splice(1, 2)
console.log(arr)
console.log("返回值", res)
// splice增加
var arr = ["kerwin", "xiaoming", "tiechui"]
var res = arr.splice(1, 0, "gangdaner", "zhugeshanzhen")
console.log(arr)
console.log("返回值", res)
reverse
反转数组中元素的顺序。返回反转后的数组。
var arr = [2, 1, 3, 4]
arr.reverse()
console.log(arr)
sort
排序
sort(fun) 如果调用该方法时没有使用参数,将按照字符编码的顺序进行排序。
var arr = [11, 21, 56, 7, 3]
// arr.sort()
// console.log(arr)//[11, 21, 3, 56, 7]
// sort接受一个回调函数
arr.sort(function (x, y) {
return x - y //升序
})
console.log(arr)
不影响原数组的方法
concat
拼接,浅拷贝
var arr1 = [1, 2, 3]
var arr2 = [4, 5, 6]
var arr3 = arr1.concat(arr2, 7, [8, 9])
console.log(arr1, arr2, arr3)
var arr4 = arr1.concat() //复制方式
arr4.pop()
console.log(arr1, arr4)
join
可以用不同的分隔符将数组转为字符串
var arr = [1, 2, 3, 4, 5]
document.write(arr.join("|"))
//arr.join("")
slice
截取,返回截取的数组(浅复制)元素形成的数组
slice([begin[, end]]),包前不包后
- 如果 end 被省略,则 slice 会一直提取到原数组末尾。如果 end 大于数组长度, slice 也会一直提取到原数组末尾。
- 如果 begin 参数为负数,则表示从原数组中的倒数第几个元素开始提取
var arr2 = arr.slice(2)
var arr2 = arr.slice(0, 2)
var arr2 = arr.slice() //复制方式
arr2.pop()
console.log(arr, arr2)
indexOf
返回找到的第一个匹配的元素的索引,-1 没找到
indexOf(searchElement) indexOf(searchElement, fromIndex)
fromIndex 可选,开始查找的位置。如果该索引值大于或等于数组长度,意味着不会在数组里查找,返回 -1。如果参数中提供的索引值是一个负值,则将其作为数组末尾的一个抵消(length+i),即 -1 表示从最后一个元素开始查找,-2 表示从倒数第二个元素开始查找,以此类推。注意:如果参数中提供的索引值是一个负值,并不改变其查找顺序,查找顺序仍然是从前向后查询数组。如果抵消后的索引值仍小于 0,则整个数组都将会被查询。
var arr = ["aaa", "bbb", "ccc", "ddd", "eee", "aaa"]
var res = arr.indexOf("aaa")
//var res = arr.indexOf("aaa",2)
// console.log(res)
lastIndexOf
从后往前找,返回找到的第一个匹配的元素的索引,-1 没找到
lastIndexOf(searchElement) lastIndexOf(searchElement, fromIndex)
fromIndex 可选,从此位置开始逆向查找。如果该值大于或等于数组的长度,则整个数组会被查找。如果为负值,将其视为从数组末尾向前的偏移(length+i),数组仍然会被从后向前查找。如果该值为负时,其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。
var res = arr.lastIndexOf("kerwin", 3)
console.log(res)
foreach
遍历
callbackFn 仅对已赋值的数组索引调用。对于稀疏数组中的空槽,它不会被调用。
forEach() 不会改变其调用的数组,但是,作为 callbackFn 的函数可以更改数组。在第一次调用 callbackFn 之前,数组的长度已经被保存。因此:
- 当调用 forEach() 时,callbackFn 不会访问超出数组初始长度的任何元素。
- 已经访问过的索引的更改不会导致 callbackFn 再次调用它们。
- 如果 callbackFn 更改了数组中已经存在但尚未访问的元素,则传递给 callbackFn 的值将是在访问该元素时的值。已经被删除的元素不会被访问。
foreach(function (currentValue, index, arr) {
//currentValue 必须。当前元素的值
//index 可选。当前元素的索引值
//arr 可选。调用方法的数组
//thisValue 可选。对象,传递给函数,执行回调用作 "this" 的值。如果省略了 thisValue,"this" 的值为window。
}, thisValue)
var arr = ["aaa", "bbb", "ccc", "ddd"]
//回调函数
arr.forEach(function (item) {
console.log(item)
})
arr.forEach(function (item, index) {
console.log(item, index)
})
arr.forEach(function (item, index, arr) {
console.log(item, index, arr)
})
map
映射,创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。
map(function (currentValue, index, arr) {}, thisValue)
var arr = ["xiaoming", "kerwin", "tiechui"]
var arr2 = arr.map(function (item) {
return "<li>" + item + "</li>"
})
console.log(arr, arr2)
document.write(arr2.join(""))
filter
过滤,返回通过测试的所有元素组成的数组(浅拷贝)。
filter(function (currentValue, index, arr) {}, thisValue)
var arr = [100, 200, 300]
var arr2 = arr.filter(function (item) {
return item > 200 //为true就加到数组中
})
console.log(arr2)
every
数组中所有元素满足条件返回 true,找到一个会返回 false 的元素,方法将会立即返回。
every(function (currentValue, index, arr) {}, thisValue)
var arr = [90, 90, 2, 94]
var res = arr.every(function (item) {
return item >= 90
})
console.log(res) //false
对于空数组,它只返回 true。(这种情况属于无条件正确,因为空集的所有元素都符合给定的条件。)。callbackFn 仅针对已分配值的数组索引调用。它不会为稀疏数组中的空槽调用。
let arr = []
arr.length = 3
// arr[1] = undefined
// delete arr[1]
let flag = arr.every(item => {
console.log(item)
return item != undefined
})
console.log(flag) //true
some
数组中只要有一个元素满足条件就返回 true
some(function (currentValue, index, arr) {}, thisValue)
var arr = [90, 90, 2, 94]
var res = arr.some(function (item) {
return item >= 90
})
console.log(res) //true
对于空数组,它只返回 false。callbackFn 仅针对已分配值的数组索引调用。它不会为稀疏数组中的空槽调用。
find
返回满足的第一个元素,否则返回 undefined。找到立即返回。
find(function (currentValue, index, arr) {}, thisValue)
var arr = [
{
grade: 90
},
{
grade: 95
},
{
grade: 100
}
]
var item = arr.find(function (item) {
return item.grade === 100
})
console.log(item)
reduce
叠加,逐个遍历数组元素,每一步都将当前元素的值与上一步的计算结果相加(上一步的计算结果是当前元素之前所有元素的总和)。
reduce(function (previousValue, currentValue, currentIndex, array) {}, initialValue)
previousValue 上一次调用 callbackFn 时的返回值。在第一次调用时,若指定了初始值 initialValue,则其值为 initialValue,否则 previousValue 将使用数组第一个元素,而 currentValue 将使用数组第二个元素。
var arr = [1, 2, 3, 4, 5]
var res = arr.reduce(function (prev, item) {
return prev + item
}, 0)
console.log(res) //15
var arr = [1, 2, 3, 4, 5]
var arr1 = []
var res = arr.reduce(function (prev, item) {
prev.push(item)
return prev
}, arr1)
console.log(res) //[1, 2, 3, 4, 5]
字符串
创建字符串
-
字面量:
-
原始值字符串,没有属性和方法(因为他们不是对象)。
-
原始值可以使用属性和方法,因为 JavaScript 在执行方法和属性时可以把原始值当作对象。
var str = "hello" -
-
构造函数创建
var str = new String("hello")var str1 = "hello" //length只读 str1.length = 0 console.log(str1) //索引/下标 只读 console.log(str1[1]) str1[1] = "a" console.log(str1)var str = "abcabcab" var obj = {} for (var i = 0; i < str.length; i++) { var key = str[i] // if(obj[key] === undefined){ // obj[key] = 1 // }else{ // obj[key]++ // } if (obj[key]) { obj[key]++ } else { obj[key] = 1 } } console.log(obj) //{a:3,b:3,c:2}
常用方法
indexOf
返回指定子字符串第一次出现的索引。未找到返回-1
indexOf(searchString) indexOf(searchString, position)
如果 position 大于调用字符串的长度,则该方法根本不搜索调用字符串。如果 position 小于零,从索引为 0 处开始找
var str = "abcda"
console.log(str.indexOf("da", 1)) //3
lastIndexOf
反向检索字符串 str,看它是否含有子串 searchValue。返回字符串中指定字符串最后一次出现的位置,没找到返回-1
str.lastIndexOf(searchValue[, fromIndex]) fromIndex 小于零,从索引为 0 处开始找
var str = "abcda"
console.log(str.lastIndexOf("abc", 3)) //0
备注: 'abab'.lastIndexOf('ab', 2) 将返回 2 而不是 0,因为 fromIndex 只限制待匹配字符串的开头。
chartAt
返回指定索引位置的字符,越界显示空字符串
var str = "kerwin"
var res = str.charAt(10)
console.log(str, res)
#### charCodeAt
返回指定索引位置字符的 Unicode 值
var res = str.charCodeAt(0)
console.log(res)
var arr = []
for (var i = 65; i < 90; i++) {
arr.push(String.fromCharCode(i))
}
console.log(arr)
toUpperCase
把字符串转换为大写
var str = "hello"
console.log(str.toUpperCase())
toLowerCase
把字符串转换为小写
var str = "HELLO"
console.log(str.toLowerCase())
replace
替换
str.replace(regexp|substr, newSubStr|function)
第一个参数为字符串时只能替换第一个,正则如果具有全局标志 g,那么 replace() 方法将替换所有匹配的子串
let str = "我爱背景天安门"
str = str.replace("背景", "北京")
console.log(str) // 输出结果:我爱北京天安门
let str = "我爱背景天安门,但是背景雾霾太严重"
str = str.replace(/背景/g, "北京")
// 输出结果:我爱北京天安门,但是北京雾霾太严重
console.log(str)
使用字符串作为参数
第二个参数中的 $ 字符具有特定的含义,它说明从模式匹配得到的字符串将用于替换。
| 字符 | 替换文本 |
|---|---|
| $n: $1、$2、...、$99 | 第一个参数是 RegExp对象,插入与 regexp 中的第 n 个子表达式(分组)相匹配的字符串。如果不存在第 n 个分组,那么将会把匹配到的内容替换为字面量。比如不存在第 3 个分组,就会用“$3”替换匹配到的内容。 |
| $& | 插入与 regexp 相匹配的子串。 |
| $` | 插入位于匹配子串左侧的文本。 |
| $' | 插入位于匹配子串右侧的文本。 |
| $$ | 插入一个 "$"。 |
//把 "Doe, John" 转换为 "John Doe" 的形式:
var str = "Doe, John"
str.replace(/(\w+)\s*, \s*(\w+)/, "$2 $1")
使用函数作为参数
这个函数是有参数的,而且这些参数是默认的。(未指定名称:用 arguments)
| 变量名 | 代表的值 |
|---|---|
| match | 匹配到的子字符串 |
| p1,p2, ... | 匹配到的第 n 个分组项 |
| offset | 匹配到的子字符串在原字符串中的索引位置 |
| string | 被匹配的原字符串 |
| NamedCaptureGroup | 命名捕获组匹配的对象 |
function replacer(match, p1, p2, p3, offset, string) {
return [p1, p2, p3].join(" - ")
}
var newString = "abc12345#$*%".replace(/([^\d]*)(\d*)([^\w]*)/, replacer)
console.log(newString) // abc - 12345 - #$*%
var txt = "刘菲:5万。张常成:5000元。孙玉杰:2000元。李明轩:2万。李子豪:8500元。"
txt = txt.replace(/(\d+)(万)/g, function () {
console.log(arguments)
return arguments[1] + "0000" + "元"
})
console.log(txt)
search
用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。未找到返回-1。
支持正则,但不执行全局匹配,还是只能找到第一个
var str = "hello World"
console.log(str.search(/World/)) //6
console.log(str.search(/world/)) //-1
console.log(str.search(/world/i)) //6
match
捕获内容,返回捕获值构成的数组,未捕获的返回 null
- 如果传递了一个非正则表达式对象,函数会执行 new RegExp(obj)将他转换成一个正则表达式对象。
- 如果正则表达式中有 g 修饰符。所有匹配到的值会按照数组格式被返回,不会返回捕获组。
- 如果没有 g 修饰符。只有第一个匹配到的值会被返回,同时还会返回第一个匹配到的值内部的所有捕获组。
var datestr = "time is from 2029-01-01 12:20:20 to 2029-11-01 12: 20:20"
console.log(datestr.match("me")) //['me', index: 2, input: 'time is from 2029-01-01 12:20:20 to 2029-11-01 12: 20:20', groups: undefined]
console.log(datestr.match(/(\d{4})-(\d{1,2})-(\d{1,2})/g)) //['2029-01-01', '2029-11-01']
slice
截取
- slice(beginIndex [,endIndex]),包前不包后
- beginIndex 为负数,则被看作是 strLength + beginIndex
- endIndex 为负数,则被看作是 strLength + endIndex
var str = "kerwin"
var str1 = str.slice(1)
var str2 = str.slice(1, 2)
var str3 = str.slice(1, -1)
console.log(str1, str2, str3) //erwin e erwi
substring
substring(开始索引 [,结束索引]),包前不包后
- 如果任一参数小于 0 或为 NaN 则被当作 0
- 如果任一参数大于 str.length,则被当作 str.length
- 如果 indexStart 大于 indexEnd,则 substring 的执行效果就像两个参数调换了一样
var anyString = "Mozilla"
// 输出 "Moz"
console.log(anyString.substring(0, 3))
console.log(anyString.substring(3, 0))
console.log(anyString.substring(3, -3))
console.log(anyString.substring(3, NaN))
console.log(anyString.substring(-2, 3))
substr
substr(开始索引 [,长度]) 如果 start 为负值,则被看作 str.length + start
var str = "kerwin"
var str1 = str.substr(1)
var str2 = str.substr(1, 2)
console.log(str1, str2) //erwin er
split
根据指定的分隔符将一个字符串分割成数组,支持正则
str.split([separator[, limit]])
var str = "a|b|c|d"
console.log(str.split("|")) //['a', 'b', 'c', 'd']
console.log(str.split("|", 2)) //['a', 'b']
concat
将一个或多个字符串与原字符串连接合并,形成一个新的字符串并返回。
var str = "abcd"
var str1 = str.concat("ef")
var str2 = str + "ef"
console.log(str1)
trim
去掉首尾空格
//trimStart() trimLeft( )去掉首空格
//trimEnd() trimRight( )去掉尾空格
var str = "hello world"
console.log("|" + str + "|")
console.log("|" + str.trimRight() + "|")
json 字符串
JSON 是用于存储和传输数据的格式。
JSON 通常用于服务端向网页传递数据。
[
{
"name": "lc",
"authors": ["Nicholas C. Zakas", "Matt Frisbie"],
"edition": 4,
"year": 2017
}
] //JSON 中的对象必须使用双引号把属性名包围起来
stringify()
将 js 序列化为 JSON 字符串
在序列化 JavaScript 对象时,所有函数和原型成员都会有意地在结果中省略。此外,值为 undefined 的任何属性也会被跳过。最终得到的就是所有实例属性均为有效 JSON 数据类型的表示。
-
参数 1:要序列化的对象
var obj = { title: "Professional JavaScript", authors: ["Nicholas C. Zakas", "Matt Frisbie"], edition: 4, year: 2017 } //这里key可以不用双引号 var str = JSON.stringify(obj) console.log(obj, str) -
参数 2:参数是过滤器,可以是数组或函数
如果第二个参数是一个数组,返回的结果只会包含该数组中列出的对象属性
const book = { title: "Professional JavaScript", authors: ["Nicholas C. Zakas", "Matt Frisbie"], edition: 4, year: 2017 } let jsonText = JSON.stringify(book, ["title", "edition"]) // jsonText: {"title":"Professional JavaScript","edition":4}提供的函数接收两个参数:属性名(key)和属性值(value)。可以根据这个 key 决定要对相应属性执行什么操作。这个 key 始终是字符串(注意,返回 undefined 会导致属性被忽略。)
const book = { title: "Professional JavaScript", authors: [ "Nicholas C. Zakas", "Matt Frisbie" ], edition: 4, year: 2017 }; let jsonText = JSON.stringify(book,(key, value) => { switch (key) { case "authors": return value.join(","); case "year": return 5000; case "edition": return undefined; default: return value; } }); // 结果如下 {"title":"Professional JavaScript","authors":"Nicholas C. Zakas,Matt Frisbie","year":5000} -
参数 3:参数是用于缩进结果 JSON 字符串的选项 在这个参数是数值时,表示每一级缩进的空格数,最大缩进值为 10,大于 10 的值会自动设置为 10。
const book = { authors: [ "Nicholas C. Zakas", "Matt Frisbie" ], edition: 4 } let jsonText = JSON.stringify(book, null, 4); // jsonText 格式如下 { "authors": [ "Nicholas C. Zakas", "Matt Frisbie" ], "edition": 4 }如果缩进参数是一个字符串而非数值,那么 JSON 字符串中就会使用这个字符串而不是空格来缩进,如果字符串长度超过 10,则会在第 10 个字符处截断。
const book = { authors: [ "Nicholas C. Zakas", "Matt Frisbie" ], edition: 4 } let jsonText = JSON.stringify(book, null, "--"); // 结果如下 { --"authors": [ ----"Nicholas C. Zakas", ----"Matt Frisbie" --], --"edition": 4 }
parse()
将 JSON 解析为 js 值
var str = `{
"name": "lc",
"age": 20
}`
var obj = JSON.parse(str)
console.log(obj)
JSON.parse()方法也可以接收一个额外的参数,是一个函数,该函数也接收两个参数,属性名(key)和属性值(value),另外也需要返回值。
如果此函数返回 undefined,则结果中就会删除相应的键。如果返回了其他任何值,则该值就会成为相应键的值插入到结果中。该函数经常被用于把日期字符串转换为 Date 对象。
const book = {
title: "Professional JavaScript",
authors: ["Nicholas C. Zakas", "Matt Frisbie"],
edition: 4,
year: 2017,
releaseDate: new Date(2022, 4, 3)
}
let jsonText = JSON.stringify(book)
let bookCopy = JSON.parse(jsonText, (key, value) =>
key == "releaseDate" ? new Date(value) : value
)
console.log(bookCopy.releaseDate.getFullYear()) // 2022
集合
Set
Set 对象是值的集合,可以按照插入的顺序迭代它的元素。Set 中的元素是唯一的。
基本操作
| 操作 | 代码 | 说明 |
|---|---|---|
| 创建 | new Set([iterable]) | 可以放入数组,Set, Map来初始化 |
| 添加元素 | set.add(5) | 返回Set本身 |
| 删除元素 | set.delete(5) | 返回bool |
| 判断是否有某个元素 | set.has(5) | 返回bool |
| 长度 | set.size | 注意与数组长度区别,数组是length |
| 清空 | set.clear() | 返回void |
遍历
let mySet = new Set([1, 3, 4, 2, 5])
//遍历
for (let item of mySet) {
console.log(item)
}
for (let item of mySet.keys()) {
console.log(item)
}
for (let item of mySet.values()) {
console.log(item)
} //Set 没有 value 只有 key,value 就是 key
for (let [item, value] of mySet.entries()) {
console.log(item) //item=value
}
//遍历结果一样: 1 3 4 2 5
Map
Map 是一组键值对的结构,有序
一个 key 只能对应一个 value,多次对一个 key 放入 value,后面的值会把前面的值覆盖掉
基本操作
| 操作 | 代码 | 说明 |
|---|---|---|
| 创建 | var myMap = new Map(); | new Map([ ['foo', 3], ['bar', {}], ['baz', undefined] ]) |
| 添加元素 | myMap.set(1,"hello") | 返回 map 本身 |
| 获取值 | myMap.get(1) | 返回对应的 value |
| 根据键删除 | myMap.delete(1) | 返回bool |
| 判断是否存在键 | myMap.has(1) | 返回bool |
| 遍历 | myMap.forEach(function (value, key,map) { }, thisValue) |
遍历
for (var key of myMap.keys()) {
console.log(key)
}
for (var value of myMap.values()) {
console.log(value)
}
for (var [key, value] of myMap) {
console.log(key + " = " + value)
}
for (var [key, value] of myMap.entries()) {
console.log(key + " = " + value)
}
myMap.forEach(function (value, key) {
console.log(key + " = " + value)
}, myMap)
特殊说明 任何一个 NaN 都不等于 NaN, 但是 NaN 可以作为 Map 的键,而且是唯一的。
var myMap = new Map()
myMap.set(NaN, "not a number")
console.log(myMap.get(NaN))
// "not a number"
var otherNaN = Number("foo")
console.log(myMap.get(otherNaN))
// "not a number"
模板字符串
// var myhtml ='<li>11111</li>\
// <li>2222</1i>\
// <1i>3333</li>'
//字符串中换行会报错,用\转义
var myhtml = `<li>11111</li>
<li>2222</li>
<li>3333</li>`
var myname = "kerwin"
// var str =`my name is`+ myname
var str = `my name is ${myname} ${10 + 20} ${10 > 20 ? "aaa" : "bbb"}`
document.write(str)
Math 对象
//random //0-1的随机数不包括1
console.log(Math.random())
//round 四舍五入取整
console.log(Math.round(4.46))
// ceil向上 floor向下
console.log(Math.floor(4.96))
//abs 绝对值
console.log(Math.abs(-10.2))
//sqrt 平方根
console.log(Math.sqrt(8))
//pow(底数,指数)
console.log(Math.pow(3, 3))
//max(多个参数)
console.log(Math.max(10, 50, 20, 34))
//min(多个参数)
console.log(Math.min(10, 50, 20, 34))
//PI
console.log(Math.PI)
Date
var date = new Date() //在不传递参数的情况下是默认返回当前时间
console.log(date) //自动转为字符串
//1个传参 毫秒值
var date1 = new Date(1000)
console.log(date1)
// 1970 1 1 0:0:1
// new Date(year, monthIndex [, day [, hours [, minutes [, seconds [, milliseconds]]]]]);
var date2 = new Date(2023, 0, 3, 10, 10, 10)
console.log(date2) //Tue Jan 03 2023 10:10:10 GMT+0800 (中国标准时间)
//字符串
// var date3=new Date("2023-10-10 10:10:10")
var date3 = new Date("2023/10/10 10:10:10")
console.log(date3)
var date = new Date()
//getFullYear()
console.log(date.getFullYear())
//getMonth() 0-11===>1-12
console.log(date.getMonth())
//getDate()
console.log(date.getDate())
//getDay()周日0周一-周六1-6
console.log(date.getDay())
// getHours
console.log(date.getHours())
console.log(date.getMinutes())
console.log(date.getSeconds())
console.log(date.getMilliseconds())
//getTime()时间戳
console.log(date.getTime())
//设置
date.setFullYear(2025)
console.log(date)
date.setMonth(5)
console.log(date)
date.setDate(25)
console.log(date)
date.setHours(13)
console.log(date)
date.setMinutes(50)
console.log(date)
date.setSeconds(59)
console.log(date)
date.setTime(1653280060490)
console.log(date)
Date 对象进行加减乘除运算时会转换成时间毫秒值
Error
Error 是 JavaScript 语言中的一个标准的内置对象,专门用于处理 JS 开发中的运行时错误。
-
当我们的 JS 代码在运行过程中发生错误的话,就会抛出 Error 对象,整个程序将会中断在错误发生的代码处,不再继续执行,这也是错误类型必须重视的原因:它会导致页面无法显示或者功能失效。
-
Error 是 JavaScript 中最原始的错误对象,作为各种异常的基础对象,还有多个衍生的具体的错误类型,这些错误对象类型在 nodejs 中也可应用。
语法
- Error 对象既可以当作构造函数使用,也可以当作方法,一般有两个可选参数:
new Error(message, options)
Error(message, options)
可选参数说明如下: message:错误信息描述 options:属性对象
属性
Error 包含 message 属性,一般还提供 name 和 stack 属性,而 cause 属性则根据定义来确定。
-
message:错误信息描述
-
name:错误的类型名字
-
stack:错误的堆栈轨迹,用于堆栈追踪
-
cause:当前错误被抛出的具体原因
const err = new Error("发生错误!", { cause: "333" }) console.log(err) // Error: 发生错误! // at <anonymous>:1:13 console.log(err.message) // '发生错误!' console.log(err.cause) // '333' console.log(err.stack) // Error: 发生错误! // at <anonymous>:1:13
需要注意的是,只有定义了 cause 属性的 Error 实例,才有该属性,否则 cause 属性不存在。
六种常见错误类型
Error 对象衍生有六种常见的错误类型,属于 Error 的派生类,属性和方法也都继承自 Error。
SyntaxError
SyntaxError 语法错误,表示 JS 代码发生的语法上的错误,包括各种不符合语法规范、书写错漏等情况。
如下变量名错误:
const 11 = 8
// Uncaught SyntaxError: Unexpected number
错误的代码语法:
Error(+)
// Uncaught SyntaxError: Unexpected token ')'
错误的值分配,给 null 赋值:
null = 1
// Uncaught SyntaxError: Invalid left-hand side in assignment
SyntaxError 语法错误无法被捕获,因为语法错误不会被执行,直接抛出错误。
TypeError
TypeError 类型错误,当代码中的变量参数等类型不对时,就会发生该类型错误。
比如,把属性当方法使用:
window.location()
// Uncaught TypeError: window.location is not a function
// at <anonymous>:1:8
错误的构造函数使用:
new null()
// Uncaught TypeError: null is not a constructor
// at <anonymous>:1:1
ReferenceError
ReferenceError 引用错误,当引用一个并不存在的变量时会发生的错误。
如直接在浏览器控制打印:
a
// Uncaught ReferenceError: a is not defined
// at <anonymous>:1:1
cons.log("")
// Uncaught ReferenceError: cons is not defined
// at <anonymous>:1:1
RangeError
RangeError 取值范围错误,当给某个对象指定一个不在取值范围内的特定值时发生的错误。
如给 Array 对象传入不合法的长度参数
Array(-1)
Array(Infinity)
// Uncaught RangeError: Invalid array length
// at <anonymous>:1:1
数字类型方法的使用:
;(5).toFixed(200)
// Uncaught RangeError: toFixed() digits argument must be between 0 and 100
// at Number.toFixed (<anonymous>)
// at <anonymous>:1:4
toFixed() 参数取值范围在 0 到 100 之间,超过则会报错。
URIError
URIError 当使用 URI 相关方法处理数据时,当这些方法的参数不正确时导致的一些错误。
主要常见于 URI 的解码等方法 decodeURI()、decodeURIComponent()等。
decodeURI("%")
decodeURIComponent("%")
// Uncaught URIError: URI malformed
// at decodeURIComponent (<anonymous>)
// at <anonymous>:1:1
EvalError
EvalError 当在使用 eval 函数时,发生的错误。eval 不被推荐使用,该错误类型一般很少见,处于半废弃状态,为了兼容考虑才存在。
自定义错误类型
使用继承 Error 对象的方式即可。
class CharError extends Error {
constructor(message = "字符错误") {
super(message)
this.name = "CharError"
}
}
以上代码使用 es6 的类继承方式,自定义一个错误类型继承 Error 对象,后续就可以使用新的 CharError 类型。
const ce = new CharError()
console.log(ce)
// CharError: 字符错误
// at error.html:26:16
如上,与 JavaScript 内置错误类型使用方式一致。
异常
-
当 JavaScript 引擎执行 JavaScript 代码时,会发生各种错误。
-
可能是语法错误,通常是程序员造成的编码错误或错别字。
-
可能是拼写错误或语言中缺少的功能(可能由于浏览器差异)。
-
可能是由于来自服务器或用户的错误输出而导致的错误。
-
当然,也可能是由于许多其他不可预知的因素。
try 和 catch
- try 语句允许我们定义在执行时进行错误测试的代码块。在 try 块中抛出错误后往后的代码不会被执行了。
- catch 可选,语句允许我们定义当 try 代码块发生错误时,所执行的代码块。
- finally 可选,语句不论之前的 try 和 catch 中是否产生异常都会执行该代码块。通常用于清理资源或关闭流。
- 每个 try 块必须跟至少一个 catch 或 finally 块,否则会抛出 SyntaxError 错误。
语法
try {
... //包含需要检查的代码
} catch(err) {
... //异常的捕获与处理
} finally {
... //结束处理
}
try..catch 无法捕获在异步代码中引发的异常,应该在异步代码内部使用 try..catch 来处理错误
try {
setTimeout(function () {
noSuchVariable // undefined variable
}, 1000)
} catch (err) {
console.log("这里不会被执行")
} //未捕获的ReferenceError将在1秒后引发
throw
-
throw 语句允许我们抛出自定义错误
-
如果把 throw 与 try 和 catch 一起使用,那么您能够控制程序流,并生成自定义的错误消息。
语法
throw exception
exception 可以是 JavaScript 字符串、数字、布尔值或对象。
function myFunction() {
var message, x
message = document.getElementById("message")
message.innerHTML = ""
x = document.getElementById("demo").value
try {
if (x == "") throw "值为空"
if (isNaN(x)) throw "不是数字"
x = Number(x)
if (x < 5) throw "太小"
if (x > 10) throw new Error("太大")
} catch (err) {
message.innerHTML = "错误: " + err
}
}
debugger
-
debugger 关键字用于停止执行 JavaScript,并调用调试函数。
-
这个关键字与在调试工具中设置断点的效果是一样的。
-
如果没有调试可用,debugger 语句将无法工作。
-
开启 debugger,代码在第三行前停止执行。
var x = 15 * 5
debugger
document.getElementbyId("demo").innerHTML = x
严格模式
严格模式即在严格的条件下运行。
为什么使用严格模式:
- 消除 Javascript 语法的一些不合理、不严谨之处,减少一些怪异行为;
- 消除代码运行的一些不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的 Javascript 做好铺垫。
"use strict"
指定代码在严格条件下执行。
"use strict"
x = 3.14 // 报错 (x 未定义)
在函数内部声明是局部作用域 (只在函数内使用严格模式):
x = 3.14 // 不报错
myFunction()
function myFunction() {
"use strict"
y = 3.14 // 报错 (y 未定义)
}
限制
不允许使用未声明的变量
"use strict"
x = { p1: 10, p2: 20 } // 报错 (x 未定义)
不允许删除变量或对象
"use strict"
var x = 3.14
delete x // 报错
不允许删除函数
"use strict"
function x(p1, p2) {}
delete x // 报错
不允许变量重名
"use strict"
function x(p1, p1) {} // 报错
不允许使用八进制
"use strict"
var x = 010 // 报错
不允许使用转义字符
"use strict"
var x = "\010" // 报错
不允许对只读属性赋值
"use strict"
var obj = {}
Object.defineProperty(obj, "x", { value: 0, writable: false })
obj.x = 3.14 // 报错
不允许对禁止扩展的对象添加新属性
"use strict"
var obj = {}
Object.preventExtensions(obj) //禁止对象有拓展属性
obj.name = 1 //报错
不允许对一个使用 getter 方法读取的属性进行赋值
"use strict"
var obj = {
get x() {
return 0
}
}
obj.x = 3.14 // 报错
不允许删除一个不允许删除的属性
"use strict"
delete Object.prototype // 报错
变量名不能使用 "eval" 字符串
"use strict"
var eval = 3.14 // 报错
变量名不能使用 "arguments" 字符串
"use strict"
var arguments = 3.14 // 报错
不允许使用以下这种语句
"use strict"
with (Math) {
x = cos(2)
} // 报错
由于一些安全原因,在作用域 eval() 创建的变量不能被调用
"use strict"
eval("var x = 2")
alert(x) // 报错
禁止 this 关键字指向全局对象
function f() {
return !this
}
// 返回false,因为"this"指向全局对象,"!this"就是false
function f() {
"use strict"
return !this
} // 返回 true,因为严格模式下,this 的值为 undefined,所以"!this"为 true。
因此,使用构造函数时,如果忘了加 new,this 不再指向全局对象,而是报错
function f() {
"use strict"
this.a = 1
}
f() // 报错,this未定义
定时器
在 js 里面,有两种定时器,倒计时定时器和间隔定时器
<button id="btn1">清除定时器-延时</button>
<button id="btn2">清除定时器-间隔</button>
//倒计时定时器,延时执行一次
//window.setTimeout,window.可以省略
var time1 = setTimeout(function () {
console.log("kerwin")
}, 2000) //注册定时器
//间隔定时器,周期间隔执行
var time2 = setInterval(function () {
console.log(new Date())
}, 1000) //注册间隔定时器
console.log(time1, time2)
// clearTimeout(time1)清除定时器
// clearInterval(time2)
console.log(btn1, btn2) //直接通过id,拿到按钮对象
btn1.onclick = function () {
console.log("btn1 click")
clearTimeout(time1)
}
btn2.onclick = function () {
console.log("btn2 click")
clearInterval(time2)
}
BOM
- BOM (Browser object Model): 浏览器对象模型
- 其实就是操作浏览器的一些能力
- 我们可以操作哪些内容
- 获取一些浏览器的相关信息(窗口的大小)
- 操作浏览器进行页面跳转
- 获取当前浏览器地址栏的信息
- 操作浏览器的滚动条
- 浏览器的信息(浏览器的版本)
- 让浏览器出现一个弹出框( alert / confirm / prompt )
- ...
- BOM 的核心就是 window 对象
window是浏览器内置的一个对象,里面包含着操作浏览器的方法
window
在 JavaScript 中,一个浏览器窗口就是一个 window 对象。
子对象
| 子对象 | 说明 |
|---|---|
| document | 文档对象用于操作页面元素 |
| location | 地址对象用于操作URL地址 |
| navigator | 浏览器对象用于获取浏览器版本信息 |
| history | 历史对象用于操作浏览器历史 |
| screen | 屏幕对象用于操作屏幕的高度和宽度 |
获取浏览器窗口大小
含滚动条
var windowHeight = window.innerHeight
console.log(windowHeight)
var windowWidth = window.innerWidth
console.log(windowWidth)
不含滚动条
console.log("宽度", document.documentElement.clientWidth)
console.log("高度", document.documentElement.clientHeight)
浏览器事件
onload
- 这个不在是对象了,而是一个事件
- 是在页面所有资源(图片、视频、dom)加载完毕后执行的
window.onload = function () {
console.log("页面已经加载完毕")
}
resize
窗口大小改变时执行
window.onresize = function () {
console.log("resize")
}
scroll
当窗口滚动时执行
window.onscroll = function () {
console.log("scroll")
}
// document.documentElement.scrollTop//获取竖直滚动距离
// document.body.scrollTop
// document.documentElement.scrollLeft//获取水平滚动距离
// document.body.scrollLeft
window.onscroll = function () {
//兼容性
console.log(document.documentElement.scrollTop || document.body.scrollTop)
if ((document.documentElement.scrollTop || document.body.scrollTop) > 100) {
console.log("显示回到顶部按钮")
} else {
console.log("隐藏回到顶部按钮")
}
}
btn.onclick = function () {
// window.scrollTo(0,0)滚动到
//1.两个数字
//2.对象
window.scrollTo({
left: 0,
top: 0
})
}
窗口操作
window.open(url,name,features)
url:
- 打开的窗口中要加载的 url,可以是一个 HTML 页面,或者其他任何浏览器能打开的资源文件。
- 允许传入空值,此时第二个参数必须传入一个已打开的窗口的名字
// 打开一个窗口 let win = window.open("https://www.baidu.com", "baidu") // 通过窗口名获取上述窗口 let refWin = window.open("", "baidu")
name:
-
该参数只是一个窗口标识,用于以后通过它来找到对应的窗口的引用。 比如在上面的例子中,我们可以通过'baidu'这个名字来找到刚打开的百度的窗口,这样就不需要在全局变量中保存该窗口的引用了(它的代价不在于内存损耗,而在于对全局变量的维护)。
-
该参数除了支持普通的名字外,还支持特殊关键字:_self、_blank、_parent 和_top,分别用于在当前窗口、新窗口、父窗口和顶级窗口中打开该窗口。 当传入了已经被使用过的窗口的名字时,不会新打开一个窗口,而是在该名字对应的窗口中打开,该窗口之前加载的内容会被替换。
-
features 窗口参数描述,字符串类型,各个参数由逗号隔开,参数之间以等号连接。 比如下面的例子可以在距当前 window 左上角(10, 10)位置处打开一个宽度 400 像素,高度 200 像素的窗口:
window.open("https://www.baidu.com", "baidu", "top=10,left=10,width=400,height=200")left,新窗口相对于当前浏览器页面左侧的距离。 top,新窗口相对于浏览器页面顶部的位置,注意,不是相对于文档区域,而是整个浏览器页面。 height,窗口内容区(即用户区,不包含工具栏、标签栏等)的高度,单位像素,最小值 100。 width,窗口的宽度(包含滚动条),单位像素,最小值 100。
返回值
window.open 返回的是对新打开的窗口的引用,即该窗口的 window 对象:
let refWin = window.open("https://www.baidu.com", "baidu")
console.log(refWin)
不过这里引用到的 window 对象并不具备完整的 DOM 属性和方法,它仅仅提供了访问该页面的一些基本属性和方法
- blur(),手动移除窗口焦点的方法,refWin.blur()可使该窗口失去焦点。
- close(),关闭该窗口的方法。
- closed,标识该窗口是否已经被关闭。
- frames,新窗口内的 frames。
- length,新窗口内 iframes 的数量。
- location,新窗口 window 的 location 对象,用于访问窗口的地址信息。
- opener,该窗口的打开者。如我们在 a 页面通过 window.open 打开 b 页面,那么 b 页面的 window.opener 就是 a 页面的 window。
- parent,该窗口的父窗口,由于是顶级窗口,因此它的值等于 window 自身。
- postMessage,通信接口,通过该方法可以实现向新窗口发送消息,优势是支持跨域。
- self、window、top,前两个均代指当前 window,top 指的是当前窗口所在页面的顶级窗口,由于自身已经是顶级窗口,因此 top 也是当前 window。
通信问题 使用 window.open 打开新窗口时,原窗口与新窗口之间是可以实现双向通信的。 在不跨域的情况下,可以直接访问页面内的任何全局变量、方法或 DOM 元素等。新窗口通过 window.opener 可以找到原窗口的 window; 而原窗口可以直接通过 window.open 的返回值访问新窗口,或者通过该窗口的名字找到该窗口,方法为:let ref = window.open('', 'windowName')。
由于新窗口的加载是异步的,因此不能在调用 window.open 之后立即访问该窗口。可以在窗口的 onload 事件内访问新窗口:
let ref = window.open('/index.html');
ref.onload = function(){
... // 与新窗口通信
}
而在新页面中,可以直接通过 window.opener 访问原窗口,如:
// 查找原窗口内的p元素
let p = window.opener.document.querySelectorAll("p")
在跨域的情况下,以上的方法会报错,因为会受到浏览器跨域安全策略的限制,此时就需要通过 window.postMessage 实现页面之间的通信。
在原窗口,可以通过对新窗口的引用调用 postMessage:
let ref = window.open("https://www.baidu.com")
ref.postMessage(data, "*")
在新窗口内,同样通过 window.opener 访问原窗口:
window.opener.postMessage(data, "*")
关闭窗口
btn.onclick = function () {
window.close()
} //关闭自己
postMessage()
语法 targetWindow.postMessage(message, targetOrigin, [transfer])
-
targetWindow:接收消息的窗口的引用
-
message:要发送到目标窗口的消息,所有数据类型
-
targetOrigin:就是指定目标窗口的来源,必须与消息发送目标相一致,可以是字符串“*”或 URI。 *表示任何目标窗口都可接收,为安全起见,请一定要明确确定接收方的 URI(例:"res.42du.cn")。
-
transfer:可选参数
接收端 目标窗口通过执行下面的 JavaScript 来侦听发送过来的消息:
window.addEventListener("message", receiveMessage)
function receiveMessage(event) {
if (event.origin !== "http://www.42du.cn") {
return
} else {
console.log(event.data)
}
}
event 对象有三个属性,分别是 origin,data 和 source。event.data 表示接收到的消息;event.origin 表示 postMessage 的发送来源,包括协议,域名和端口;event.source 表示发送消息的窗口对象的引用; 我们可以用这个引用来建立两个不同来源的窗口之间的双向通信。
弹出框
// alert
btn.onclick = function () {
alert("用户名密码不匹配")
}
//询问框
btn.onclick = function () {
var res = confirm("你确定删除吗?")
console.log(res)
if (res) {
} else {
}
}
//输入框prompt
btn.onclick = function () {
var res = prompt("请输入你的用户名")
console.log(res)
}
一般不用, 会阻塞后续代码执行
location
用于获取或设置窗体的 URL,并且可以用于解析 URL
| location对象的属性 | 返回值 |
|---|---|
| location.href | 获取或者设置url,会把中文变成 url 编码的格式 |
| location.search | 获取或者设置当前页面地址“?”后面的内容 |
| location.hash | 获取和设置当前页面地址#后边的内容,一般用于锚点链接 |
| location对象的方法 | 返回值 |
|---|---|
| location.assign("url") | 跟href 一样,可以跳转页面(也称为重定向页面) |
| location.replace("url") | 替换当前页面,因为不记录历史,所以不能后退页面 |
| location.reload() | 重新加载页面,相当于刷新按钮或者f5,如果参数为true强制刷新ctrl+f5 |
location.href
console.log(window.location.href)
location.href = "./index.html"
//这个就会跳转页面到后面你给的那个地址,当前窗口打开
history
window 中有一个对象叫做 history,是专门用来存储历史记录信息的
history.length 返回浏览器历史列表的 url 数量
history.back()
- history.back()是用来回退历史记录的,就是回到前一个页面,就相当于浏览器上的<-按钮
window.history.back()- 前提是你要有上一条记录,不然就是一直在这个页面,也不会回退
history.forward()
- history.forward()是去到下一个历史记录里面,也就是去到下一个页面,就相当于浏览器上的->按钮
window.history.forward()- 前提是你要之前有过回退操作,不然的话你现在就是最后一个页面,没有下一个
history.go()
history.go(1) //1前进一个页面,-1后退一个页面
screen
包含有关客户端显示屏幕的信息。
| 属性 | 说明 |
|---|---|
| screen.availHeight | 返回屏幕的高度(不包括Windows任务栏) |
| screen.availWidth | 返回屏幕的宽度(不包括Windows任务栏) |
| screen.width | 返回屏幕的总宽度 |
| screen.height | 返回屏幕的总高度 |
本地存储
-
localStorage 永久存储
//增/改 localStorage.setItem("name", "kerwin") //取 localStorage.getItem("name") //删 localStorage.removeItem("name") //清空 localStorage.clear()localStorage 只要在相同的协议、相同的主机名、相同的端口下,就能读取/修改到同一份 localStorage 数据。
-
sessionStorage 会话存储,关闭页面就消失,可以刷新
//增/改 sessionStorage.setItem("name", "kerwin") //取 sessionStorage.getItem("name") //删 sessionStorage.removeItem("name") //清空 sessionStorage.clear()sessionStorage 比 localStorage 更严苛一点,除了协议、主机名、端口外,还要求在同一窗口(也就是浏览器的标签页)下。
-
只能存字符串,不能存对象
-
一个 key 对应一个 value,实现修改
DOM
- DOM (Document object Mode1) ): 文档对象模型
- 其实就是操作 html 中的标签的一些能力
- 我们可以操作哪些内容
- 获取一个元素
- 移除一个元素
- 创建一个元素
- 向页面里面添加一个元素
- 给元素绑定一些事件
- 获取元素的属性
- 给元素添加一些 css 样式
- DOM 的核心对象就是 docuemnt 对象
- document 对象是浏览器内置的一个对象,里面存储着专门用来操作元素的各种方法
- DOM: 页面中的标签,我们通过 js 获取到以后,就把这个对象叫做 DOM 对象
获取节点
<div id="box"></div>
// console.log(box)直接通过id获得,不推荐
// box.innerHTML = "11111"
console.log(document.documentElement) //获取html标签
console.log(document.head) //获取head标签
console.log(document.body) // 获取body标签
getElementById
返回一个匹配特定 id 的元素
不支持 element.getElementById()
var obox = document.getElementById("box")
obox.innerHTML = "22222"
getElementsByClassName
支持 element.getElementsByClassName(),返回后代中包含指定类名的所有元素的 HTML 集合 HTMLCollection
var items = document.getElementsByClassName("newsitem") //返回伪数组,数组的有些方法无法使用
for (var i = 0; i < items.length; i++) {
items[i].innerHTML = "news-" + i
}
var newitems = Array.from(items)
console.log(newitems.filter)
getElementsByTagName
支持 element.getElementsByTagName(),返回后代中包含指定标签名的所有元素的 HTML 集合 HTMLCollection
var items = document.getElementsByTagName("li")
console.log(items)
getElementsByName
返回一个动态 NodeList 集合,包含 name 属性为指定值的所有元素
不支持 element.getElementsByName()
<input name="username" type="text" />
var item = document.getElementsByName("username")
console.log(item[0])
item[0].value = "kerwin"
querySelector
支持 element.querySelector(),返回后代中与指定的选择器匹配的第一个元素
var item = document.querySelector(".newsitem")
console.log(item)
querySelectorAll
支持 element.querySelectorAll(),返回后代中与指定的选择器匹配的所有元素组成的静态 NodeList
var items = document.querySelectorAll("ul li.newsitem")
console.log(items)
操作属性
原生属性
<input type="text" value="hello" id="username" />
<input type="checkbox" checked id="rember" />
<img src="" alt="" id="photo" />
username.type = "password"
rember.checked = false
photo.src = "01.png"
console.log(photo.src)
自定义属性
//setAttribute getAttribute removeAttribute,也可以操作原生属性
//增/改
box.setAttribute("tiechui", "222222")
//取
console.log(box.getAttribute("tiechui"))
//删
box.removeAttribute("tiechui")
box.removeAttribute("kerwin")
var oitems = document.getElementsByTagName("li")
for (var i = 0; i < oitems.length; i++) {
oitems[i].setAttribute("index", i)
}
//h5 ===>约定data-****** .dataset
console.log(box.dataset)
box.dataset.xiaoming = "Hello3" //字母会转成小写
delete box.dataset.xiaoming
console.log(box.attributes)
console.log(box.attributes[0]) //不能设置
innerHTML
<div id="box">
hello world
<div>kerwin</div>
</div>
<input type="text" id="username" value="hello" />
<select name="" id="select">
<option value="1">11111</option>
<option value="2" selected>2222</option>
<option value="3">3333</option>
</select>
// innerHTML
// innerText
// value
console.log(box.innerHTML) //获取文本和标签结构
box.innerHTML += "<h1>11111111</h1>"
console.log(box.innerText) //获取只有文本
box.innerText = "<h1>1111111</h1>" //不解析html
console.log(box.value) //undefined
username.value = "2222222"
console.log(select.value)
select.value = "3" //显示value="3"的选项
操作样式
<div id="box" style="width:100px;color:black;background-color:yellow;">11111</div>
- XX.style.属性:只能读取到行内样式方法,可以添加和修改行内样式
- getComputedStyle 能获取内部样式、外部样式、行内样式,不能赋值写样式。
console.log(box.style.width)
console.log(box.style["background-color"])
console.log(box.style.backgroundColor) //多个单词-驼峰
box.style.width = "200px"
box.style.backgroundColor = "red"
var obox = document.getElementById("box")
//var res=getComputedStyle(obox)["background-color"]
var res = getComputedStyle(obox).backgroundColor
console.log(res)
操作类名
<div id="box" class="item item1 item2">kerwin</div>
// .className
// console.log(box.className)
// box.className ="item item2"
box.className = "item item2 item2 item3"
// classList属性,自动去重
console.log(box.classList)
// box.classList.add("item2")
box.classList.remove("item1")
box.classList.remove("item2")
//切换toggle
box.classList.toggle("item") //如果有item就删除,否则就加上
Node
- DOM 的节点我们一般分为常用的三大类元素节点/文本节点/属性节点
- 比如我们在获取元素的时候,通过各种方法获取到的我们叫做元素节点(标签节点)
- 比如我们标签里面写的文字,那么就是文本节点
- 写在每一个标签上的属性,就是属性节点
元素节点
- 我们通过 getElementBy...获取到的都是元素节点
属性节点
- 我们通过 getAttribute 获取的就是元素的属性节点
文本节点
- 我们通过 innerText 获取到的就是元素的文本节点,包含回车、空格
<div>
kerwin
<p>111111</p>
<!--我是注释-->
</div>
div 中的节点
- \n kerwin \n
<p>111111</p>- \n
<!--我是注释-->- \n
// parentNode Vs parentElement
// 1.文本不能做父节点,大部分情况下两个结果相同
// 2.parentNode能找到document, 而parentElement不能
// childNodes属性 children
console.log(box.childNodes) //所有子节点
console.log(box.children) //所有子元素
// firstChild firstElementChild
console.log(box.firstChild) //第一个子节点
console.log(box.firstElementChild) //第一个元素子节点
// lastChild lastElementChild
console.log(box.lastChild)
console.log(box.lastElementChild)
// previousSibling previousElementSibling
console.log(box.previousSibling) //前一个兄弟节点
console.log(box.previousElementSibling) //前一个元素兄弟节点
// nextSibling nextElementSibling
console.log(box.nextSibling) //下一个兄弟节点
console.log(box.nextElementSibling) //下一个元素兄弟节点
操作 DOM 节点
<div id="box">
<div id="child">11111111</div>
</div>
-
创建节点
var odiv = document.createElement("div") odiv.className = "aaaa" odiv.id = "aaa" odiv.style.background = "yellow" odiv.innerHTML = "我是新创建的节点" console.log(odiv) -
增加节点
//box.appendChild(odiv) //insertBefore(要插入的节点,谁的前面) box.insertBefore(odiv, child) -
删除节点
// box.removeChild(child) box.remove() //删除自己以及后代 -
替换节点
//replaceChild(新的节点, 老的节点) var odiv2 = document.createElement("div") odiv2.innerHTML = "2222222" box.replaceChild(odiv2, child) -
克隆节点
var oCloneBox = box.cloneNode(true) //false 不克隆后代默认,true 克隆后代 console.log(oCloneBox) oCloneBox.id = "box2" document.body.appendChild(oCloneBox) -
修改页面中的某一个节点
-
获取页面中的某一个节点
节点属性
先准备一段代码
<ul test="我是ul的一个属性">
<li>hello</li>
</ul>
//先获取ul
var oUl = document.querySelector("ul")
//获取到ul下的第一个子元素节点,是一个元素节点
var eleNode = oUl.firstElementChild
//获取到ul的属性节点组合,因为是个组合,我们要拿到节点的话要用索引
var attrNode = oul.attributes[0]
//获取到ul下的第一个子节点,是一个文本节点
var textNode = oUl.firstChild
nodeType
-
nodeType: 获取节点的节点类型,用数字表示
console.log(eleNode.nodeType) // 1 console.log(attrNode.nodeType) // 2 console.log(textNode.nodeType) // 3- nodeType === 1 就表示该节点是一个元素节点
- nodeType === 2 就表示该节点是一个属性节点
- nodeType === 3 就表示该节点是一个文本节点
nodeName
-
nodeName :获取节点的节点名称
console.log(eleNode.nodeName) // LI console.log(attrNode.nodeName) // test console.log(textNode.nodeName) // #text- 元素节点的 nodeName 就是大写标签名
- 属性节点的 nodeName 就是属性名
- 文本节点的 nodeName 都是#text
nodeValue
- nodevalue :获取节点的值
console.log(eleNode.nodeValue) // null console.log(attrNode.nodeValue) // 我是ul的一个属性 console.log(textNode.nodeValue) //换行+空格- 元素节点没有 nodeValue
- 属性节点的 nodeValue 就是属性值
- 文本节点的 nodeValue 就是文本内容
| - | nodeType | nodeName | nodeValue |
|---|---|---|---|
| 元素节点 | 1 | 大写标签名 | null |
| 属性节点 | 2 | 属性名 | 属性值 |
| 文本节点 | 3 | #text | 文本内容 |
获取元素尺寸
- 就是获取元素的"占地面积"
offsetWidth 和 offsetHeight
- offsetwidth :获取的是元素内容+ padding + border 的宽度
- offsetHeight :获取的是元素内容+ padding + border 的高度
clientWidth 和 clientHeight
- clientwidth :获取的是元素内容+ padding 的宽度
- clientHeight :获取的是元素内容+ padding 的高度
注意:
- 获取到的尺寸是没有单位的数字
- 当元素在页面中不占位置的时候,获取到的是 0 display:none;元素在页面中不占位
偏移量
<style>
*{
margin:0;
padding:0;
}
#box{
width: 500px;
height: 500px;
background: yellow;
overfLow: hidden;
}
#myparent{
width: 300px;
height: 300px;
background: blue;
overflow: hidden;
}
#child{
width: 100px;
height: 100px;
background: red;
}
div{
margin:50px;
}
</style>
</head>
<body>
<div id="box">
<div id="myparent">
<div id="child"></div>
</div>
</div>
<script>
console.log(child.offsetLeft,child.offsetTop)
console.log(myparent.offsetLeft,myparent.offsetTop)
console.log(box.offsetLeft, box.offsetTop)
//参考点是定位父级,如果父级元素都没有定位,偏移量相对于body
</script>
</body>
console.log(list.clientLeft, list.clientTop)
//左边框、上边框的宽度,只读
事件
一个事件由什么东西组成
-
触发谁的事件:事件源
-
触发什么事件:事件类型
-
触发以后做什么:事件处理函数
var oDiv = document.querySelector(".div") oDiv.onclick = function () { console.log("你点击了div") } //事件源:oDiv //事件类型:click //function () {}:事件的处理函数 -
当我们点击 div 的时候,就会执行事件处理函数内部的代码
-
每点击一次,就会执行一次事件处理函数
绑定方式
<div id="box">aaaa</div>
<div id="box2">bbbb</div>
dom0 同一类型事件后面的会覆盖前面的
box.onclick = function () {
console.log("11111")
}
//box.click()触发
box.onclick = function () {
console.log("22222")
}
dom2 绑定多个事件处理函数按照顺序执行
box2.addEventListener("click", function () {
console.log("111111")
})
box2.addEventListener("click", function () {
console.log("222222")
})
box2.addEventListener("click", function () {
console.log("3333333")
})
事件解绑
- dom0
btn.onclick = function () {
console.log("谢谢惠顾")
this.onclick = null
}
- dom2
function handler() {
console.log("谢谢惠顾")
this.removeEventListener("click", handler)
}
btn.addEventListener("click", handler)
常见事件
HTML 事件可以是浏览器行为,也可以是用户行为。
浏览器事件
- load : 页面全部资源加载完毕
- scroll : 浏览器滚动的时候触发
- ...
鼠标事件
- click : 点击事件
- dblclick : 双击事件
- contextmenu : 右键单击事件
- mousedown : 鼠标左键按下事件
- mouseup : 鼠标左键抬起事件
- mousemove : 鼠标移动
- mouseover : 鼠标移入事件
- mouseout : 鼠标移出事件
//移入、移出子元素也会触发 box.onmouseover = function () { console.log("移入") } box.onmouseout = function () { console.log("移出") } - mouseenter : 鼠标移入事件
- mouseleave : 鼠标移出事件
- ...
键盘事件
- keyup :键盘抬起事件
- keydown :键盘按下事件
- keypress :键盘按下再抬起事件
- ...
一般给 window、document、输入框 input 加
<input type="text" id="username" />
username.onkeydown = function () {
console.log("按下键盘", "判断是不是回车键")
}
username.onkeyup = function () {
console.log("抬起键盘", "判断是不是回车键")
}
表单事件
- change : 表单内容改变事件,获取焦点和失去焦点时内容不同时触发
- input : 表单内容输入事件
- focus : 表单获得焦点
- blur : 表单失去焦点
- submit : 表单提交事件
- reset : 表单重置事件
- ...
触摸事件
针对移动端
- touchstart : 触摸开始事件
- touchend : 触摸结束事件
- touchmove : 触摸移动事件
- ...
事件对象
每一个事件都会有一个对应的对象来描述这些信息,我们就把这个对象叫做事件对象
例如:
- 你触发一个点击事件的时候,你点在哪个位置了,坐标是多少
- 你触发一个键盘事件的时候,你按的是哪个按钮
- ...
username.onkeyup = function (evt) {
console.log(evt.keyCode)
if (evt.keyCode === 13) {
console.log("创建节点")
}
}
box.onclick = function (evt) {
console.log(evt)
}
点击事件光标坐标获取
-
clientX clientY 距离浏览器可视窗口左上角的坐标值
-
pageX pageY 距离页面文档流的左上角的坐标值
-
offsetX offsetY 距离实际触发元素的左上角的坐标值
box.onclick = function (evt) {
console.log(evt.clientX, evt.clientY)
console.log(evt.pageX, evt.pageY)
console.log(evt.offsetX, evt.offsetY)
}
事件传播
- 当元素触发一个事件的时候,其父元素也会触发相同的事件,父元素的父元素也会触发相同的事件
- 就像上面的图片
- 点击在红色盒子上的时候,会触发红色盒子的点击事件
- 也是点击在了粉色的盒子上,也会触发粉色盒子的点击事件
- 也是点击在了 body 上,也会触发 body 的点击事件
- 也是点击在了 html 上,也会触发 html 的点击事件
标准的 dom 事件流:
- 捕获: window=>document=>body=>outer
- 目标:inner
- 冒泡:outer=>body=>document=>window
默认情况只在冒泡触发,按照 dom2 事件绑定,并进行配置才能看到捕获的回调函数被触发。
inner.addEventListener("click", function () {
console.log("inner")
})
center.addEventListener("click", function () {
console.log("center")
})
outer.addEventListener("click", function () {
console.log("outer")
})
document.body.addEventListener("click", function () {
console.log("document.body")
})
document.documentElement.addEventListener("click", function () {
console.log("document.docuementElement")
})
window.addEventListener("click", function () {
console.log("window")
})
inner.addEventListener(
"click",
function () {
console.log("inner-捕获")
},
true
)
center.addEventListener(
"click",
function () {
console.log("center-捕获")
},
true
)
outer.addEventListener(
"click",
function () {
console.log("outer-捕获")
},
true
)
document.body.addEventListener(
"click",
function () {
console.log("document.body-捕获")
},
true
)
document.documentElement.addEventListener(
"click",
function () {
console.log("document.docuementElement-捕获")
},
true
)
window.addEventListener(
"click",
function () {
console.log("window-捕获")
},
true
)
window-捕获 document.docuementElement-捕获 document.body-捕获 outer-捕获 center-捕获 inner-捕获 inner center outer document.body document.docuementElement window
阻止事件传播
window.addEventListener("click", function (evt) {
evt.stopPropagation()
})
阻止默认行为
dom0 return false 阻止默认行为
document.oncontextmenu = function () {
console.log("右键单击.自定义右键菜单")
return false
}
dom2 evt.preventDefault()
document.addEventListener("contextmenu", function (evt) {
console.log("右键单击.自定义右键菜单")
evt.preventDefault()
})
事件委托
- 因为冒泡机制,点击子元素的时候,也会同步触发父元素的相同事件,所以我们就可以把子元素的事件委托给父元素来做
- 减少多个函数的绑定的性能损耗
- 动态添加元素,也会有事件处理
事件触发
-
点击子元素的时候,不管子元素有没有点击事件,只要父元素有点击事件,那么就可以触发父元素的点击事件
<body> <ul> <li>1</li> <li>2</li> <li>3</li> </ul> <script> var oUl = document.querySelector("ul") oUl.addEventListener("click", function (e) { console.log("我是ul的点击事件,我被触发了") }) </script> </body>- 点击 ul 的时候会触发,点击 li 的时候,其实也会触发
target
-
target:实际触发事件的对象,通常等于 currentTarget
-
当你触发点击事件的时候,你点击在哪个元素上, target 就是哪个元素
<body> <ul> <li>1</li> <li>2</li> <li>3</li> </ul> <script> var oUl = document.querySelector("ul") oUl.addEventListener("click", function (e) { console.log(e.target) if (e.target.nodeName === "LI") console.log("li被点击") }) </script> </body>- 上面的代码,当你点击 ul 的时候,target 就是 ul
- 当你点击在 li 上面的时候,target 就是 li
currentTarget:绑定事件的对象,在不使用箭头函数时等于 this
正则表达式
-
正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串搜索模式。
-
搜索模式可用于文本搜索和文本替换。
语法
/正则表达式主体/修饰符(可选)
<form action="">
<input type="text" required id="mytext" />
<input type="email" />
<input type="submit" value="submit" />
</form>
//1.字面量 / /
var reg = /abc/i
console.log(reg)
//2.内置构造函数
var reg2 = new RegExp("abc", "i")
var reg2 = new RegExp(/abc/, "gi")
mytext.onblur = function () {
console.log(reg.test(mytext.value)) //验证是否包含reg中的内容
}
test()
- test() 方法用于检测一个字符串是否匹配某个模式,如果字符串中含有匹配的文本,则返回 true,否则返回 false。
exec()
-
exec() 捕获片段,默认只能捕获第一次出现的
-
该函数返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。
捕获一次
var datestr = "time is 2029-01-01 2023-05-23"
var reg = /\d{4}-\d{1,2}-\d{1,2}/
var newdatestr = reg.exec(datestr)
console.log(newdatestr) //['2029-01-01', index: 8, input: 'time is 2029-01-01 2023-05-23', groups: undefined]
全局捕获
var reg = /\d{4}-\d{1,2}-\d{1,2}/g
var newdatestr1 = reg.exec(datestr)
console.log(newdatestr1) //['2029-01-01', index: 8, input: 'time is 2029-01-01 2023-05-23', groups: undefined]
var newdatestr2 = reg.exec(datestr)
console.log(newdatestr2) //['2023-05-23', index: 19, input: 'time is 2029-01-01 2023-05-23', groups: undefined]
var newdatestr3 = reg.exec(datestr)
console.log(newdatestr3)
console.log(newdatestr1[0].split("-").join("/"))
console.log(newdatestr2[0].split("-").join("/"))
分组捕获:加了小括号后数组第一个元素是整体捕获的内容,接下来的元素是该整体内容对应的每个小括号的内容
var reg = /(\d{4})-(\d{1,2})-(\d{1,2})/g
var newdatestr1 = reg.exec(datestr)
console.log(newdatestr1) //['2029-01-01', '2029', '01', '01', index: 8, input: 'time is 2029-01-01 2023-05-23', groups: undefined]
var newdatestr2 = reg.exec(datestr)
console.log(newdatestr2)
var newdatestr3 = reg.exec(datestr)
console.log(newdatestr3)
console.log(newdatestr1[0].split("-").join("/"))
console.log(newdatestr2[0].split("-").join("/"))
元字符
- \d 一位数字(0-9)
var reg = /\d\d/
console.log(reg.test("abc22")) //true
console.log(reg.test("123")) //true
console.log(reg.test("a1")) //false
- \D 一位非数字
var reg = /\Dk\D/
console.log(reg.test("1k23")) //false
console.log(reg.test("123")) //false
console.log(reg.test("aka")) //true
- \s 一位空白(空格缩进换行)
var reg = /\s/
console.log(reg.test("12 3a")) //true
console.log(reg.test("12\n3")) //true
console.log(reg.test("1")) //false
console.log(reg.test("a ba")) //true
- \S 一位非空白
var reg = /\S/
console.log(reg.test("12 3a")) //true
console.log(reg.test(" ")) //false
console.log(reg.test("\n\n\n")) //false
console.log(reg.test("")) //false
- \w 字母数字下划线
var reg = /\w\w/
console.log(reg.test("&*")) //false
console.log(reg.test("12abc")) //true
console.log(reg.test("a")) //false
console.log(reg.test("1")) //false
console.log(reg.test("_")) //false
- \W 非字母数字下划线
var reg = /\W\W/
console.log(reg.test("&*")) //true
console.log(reg.test("12abc")) //false
console.log(reg.test("a")) //false
console.log(reg.test("1")) //false
console.log(reg.test("_")) //false
- . 任意内容(除了换行)
var reg = /./
console.log(reg.test("%^*#$")) //true
console.log(reg.test("ba\ncd")) //true
console.log(reg.test("123")) //true
console.log(reg.test("\n\n\n\n\n")) //false
- \ 转义字符
var reg = /\d\.\d/ //1.2 2.3
console.log(reg.test("1.2")) //true
console.log(reg.test("1a2")) //false
边界符
- ^ 开头
var reg = /^\d/ //开头满足\d
console.log(reg.test("aabb2")) //false
console.log(reg.test("1aabb")) //true
- $ 结尾边界
var reg = /\d$/
console.log(reg.test("aabb2")) //true
console.log(reg.test("1aabb")) //false
- ^开头结束$
var reg = /^a\dc$/ //只能是a数字c
console.log(reg.test("abc")) //false
console.log(reg.test("abcd")) //false
console.log(reg.test("dabc")) //false
console.log(reg.test("a6c")) //true
限定符
修饰紧挨着的一个,要求连续出现
- * 0~多次
var reg = /\d*/
console.log(reg.test("abc")) //true
console.log(reg.test("abc1")) //true
console.log(reg.test("abc12")) //true
- + 1~多次
var reg = /\d+/
console.log(reg.test("abc")) //false
console.log(reg.test("abc1")) //true
- ? 0~1 次
var reg = /\d?/
console.log(reg.test("abc")) //true
console.log(reg.test("ab1")) //true
- {n, } 大于等于 n 次
var reg = /\d{3,}/
// var reg=/a{3}/等于3个
console.log(reg.test("aaa")) //false
console.log(reg.test("ab12")) //false
console.log(reg.test("ab123")) //true
- {n,m} 大于等于 n 并且小于等于 m
var reg = /\d{3,5}/
console.log(reg.test("aaa")) //false
console.log(reg.test("ab12")) //false
console.log(reg.test("ab123")) //true
console.log(reg.test("ab123456")) //true,因为test只要包含就满足
//用 exec()可以看出区别
特殊符号
- ()整体、分组
var reg = /(abc){2}/
console.log(reg.test("abc")) //false
console.log(reg.test("decc")) //false
console.log(reg.test("332abcabc4343")) //true
console.log(reg.test("abcabc")) //true
- | 或,左右的内容分别是一个整体
/abc|def/等价于(abc)|(def)
var reg = /a|b/
console.log(reg.test("123")) //false
console.log(reg.test("1a3")) //true
console.log(reg.test("12b")) //true
var reg2 = /(abc|def)c*/
console.log(reg2.test("abcdf")) //true
var reg3 = /abc|def|xyz/
console.log(reg3.test("abc")) //true
console.log(reg3.test("def")) //true
console.log(reg3.test("xy")) //false
- [] 代表其中 1 个
var reg = /[a-w]{3,5}/
console.log(reg.test("abcd")) //true
console.log(reg.test("abxyz")) //false
console.log(reg.test("111abxy222")) //false
- [^abc] 不在范围内
var reg2 = /[^abc]/
console.log(reg2.test("a")) //false
console.log(reg2.test("abz")) //true
命名捕获分组
优点:
- 起到注释的作用,代码清晰
- 修改了正则表达式,比如说在之前的分组前又添加了一份分组,不需要重新修改第二个参数中的顺序
语法
命名捕获分组的语法是 (?<name>...),比普通的分组多了一个 ?<name> 字样,其中 name 的起法就和你平时起变量名一样即可(不过在这里关键字也可用)。
反向引用一个命名分组的语法是 \k<name>,注意命名分组同样可以通过数字索引来反向引用,比如:
/(?<foo>a)\k<foo>\1/.test("aaa") // true
在 replace() 方法的替换字符串中反向引用是用 $<name>,同样 $1 仍然可用
"abc".replace(/(?<foo>a)/, "$<foo>-") // "a-bc"
在 API 中的使用
-
在 exec() 和 match() 中的使用:
const groups = "04-25-2017".match(/(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/).groups // {month: "04", day: "25", year: "2017"} const { day, month, year } = groupsexec() 和 match() 方法返回的匹配结果数组上多了一个 groups 属性,里面存放着每个命名分组的名称以及它们匹配到的值,注意这个 groups 属性只有在当前正则里至少存在一个命名分组的前提下才会存在
-
在 replace(/.../, replacement) 中的使用:
function toLocalDate(date) { return date.replace( /(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/, "$<day>-$<month>-$<year>" ) }"04-25-2017".replace(/(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{4})/, (...args) => { const groups = args.slice(-1)[0] const { day, month, year } = groups return `${day}-${month}-${year}` }) // "25-04-2017"也就是说,在实参列表的最末尾,多传了一个对象。同样,如果正则里没有命名分组,这个参数不会存在。
异常情况
分组名不能有重复项:
/(?<foo>a)(?<foo>b)/ // SyntaxError: Duplicate capture group name
反向引用一个不存在的分组名:
/\k<foo>/u // SyntaxError: Invalid named capture referenced
/\k<foo>/.test("k<foo>") // true, 非 Unicode 下为了向后兼容,k 前面的 \ 会被丢弃
在 reaplce() 方法的替换字符串中引用一个不存在的分组:
"abc".replace(/(?<foo>.*)/, "$<bar>") // SyntaxError: Invalid replacement string
"abc".replace(/(.*)/, "$<bar>") // "$<bar>",不包含命名分组时会向后兼容
修饰符
-
i执行对大小写不敏感的匹配。var myreg = /[a-z]/i //忽略大小写 console.log(myreg.test("AA")) //true console.log(myreg.exec("AA")[0]) //A // /\d/ig -
g执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。 -
m多行搜索var reg = new RegExp("od") var str = "so good\n so good" var result = str.replace(reg, "hi") console.log(result) //so gohi //so good var reg = new RegExp("od", "gm") //加上m标签表示多行匹配 var str = "so good\n so good" var result = str.replace(reg, "hi") console.log(result) //so gohi //so gohi
- 贪婪
var reg = /\d{1,4}/ console.log(reg.exec("aa123456bb")) - 懒惰 捕获尽可能少的
*? +? ?? {n,}? {n,m}?
var reg = /\d{1,4}?/ console.log(reg.exec("aa123456bb"))
对象简写
mybtn.onclick = function () {
let username = myusername.value
let password = mypassword.value
console.log(username, password)
var obj = {
username, //username:username,名称一样,右边是变量
password
}
console.log("发给后端的结构", obj)
}
var obj = {
a: 1,
getName() {
console.log(this.a)
}
//getName:function() {
// console.log(this.a)
//}
}
obj.getName()
let 和 const 关键字
- 必须先定义再使用
- 不允许变量重名
- 使用 let/const 关键字来实现块级作用域。
- 声明的变量只在所在的代码块内有效。
暂时性死区
ES6 规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。总之,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”,也就是说使用 let 声明的变量都是先声明再使用 ,不存在变量提升问题。
let myname = "sxx"
{
console.log(myname)
let myname = "sdx"
} //报错
报错原因:在块作用域内,let 声明的变量被提升,但变量只是创建被提升,初始化并没有被提升(初始化就是给变量先赋值成 undefined),在初始化之前使用变量,就会形成一个暂时性死区。
// var 关键字声明的全局作用域变量属于window对象:
var carName = "Volvo"
// 可以使用 window.carName 访问变量
// let 关键字声明的全局作用域变量不属window 对象:
let carName = "Volvo"
// 不能使用 window.carName 访问变量
const 用于声明一个或多个常量,声明时必须进行初始化,且初始化后值不可再修改(与 let 的区别),对象类型可改变内部属性。
const name = "kerwin"
name = "xiaoming" //不能重新赋值
let list = document.querySelectorAll("li") // 6个li
for (var i = 0; i < list.length; i++) {
list[i].onclick = function () {
console.log(i)
}
}
//console.log(i) 6
上面这段代码会输出什么呢?答案是 6 个 6,因为 var 关键字会使变量提升,所以其实只有一个全局 i,当你去执行 click 事件的时候,这时候 i 已经变成 6 了。
那么把 var 改成 let 之后呢
let list = document.querySelectorAll("li") //6个li
for (let i = 0; i < list.length; i++) {
list[i].onclick = function () {
console.log(i)
}
}
//console.log(i)报错
这段代码执行效果,依次点击会输出 0,1,2,3,4,5。 其实这里有两个点:
- 每次进入循环体,都会在循环体中重新声明一个 i 并初始化一次
- for 后面的括号里有一个作用域,所以其实一共存在 7 个 i,六次循环,然后一个结束条件。
箭头函数
- 只有一个形参的时候()可以省略
- 只有一句代码时{}可以省略
- 只有返回值的时候,省略 return
- 没有 arguments
- 箭头函数没有 this,箭头函数 this 是父级作用域的
- 不能作为构造函数
// var test = (a,b) => {
//console. log(111,a,b)
// }
// test("kerwin" ,100)
// var test = () => ({
// name: "kerwin",
// }) //要加小括号
// var test = a => 100 * a
// console.log(test(10))
var test = function () {
// console.log(a,b,c)
console.log(arguments[0], arguments[1], arguments[2])
}
test(1, 2, 3)
//arguments是保存着所有实参的伪数组,所以不用写形参也可以。
mytext.oninput = function () {
// var that = this
setTimeout(() => {
console.log(this.value)
}, 1000) //window调用,但这里的this是mytext
}
函数默认参数
一、基本用法
function first(x = 1, y = 2) {
console.log("x:" + x, "y:" + y)
}
first() //x:1 y:2
first(100) //x:100 y:2
二、与解构赋值默认值结合
function second({ x, y = 2 }) {
console.log("x:" + x, "y:" + y)
}
second({}) //x:undefined y:2
second({ x: 100 }) //x:100 y:2
second({ x: 100, y: 200 }) //x:100 y:200
没有默认值,每次都要传“{}”就会显得很麻烦
三.双重默认值
function third({ x = 1, y = 2 } = {}) {
console.log("x:" + x, "y:" + y)
}
third() //x:1 y:2
third({ x: 100 }) //x:100 y:2
third({ x: 100, y: 200 }) //x:100 y:200
解构赋值
快速地从对象和数组中获取里面的成员
数组解构
按顺序匹配
let [a, [b], d] = [1, [2, 3], 4]
//a 1
//b 2
//d 4
//跳过数组元素
let [a, , d] = [1, [2, 3], 4]
//a 1
//d 4
数组是一个特殊的对象
let arr = [1, 2, 3]
let { 0: first, [arr.length - 1]: last } = arr
//first 1
//last 3
对象解构
-
对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: "aaa", bar: "bbb" } //foo "aaa" //bar "bbb" let { baz } = { foo: "aaa", bar: "bbb" } //baz undefinedconst node = { loc: { start: { line: 1, column: 5 } } } let { loc, //获取loc Object {start: Object} loc: { start }, //只能获取start Object {line: 1, column: 5} loc: { start: { line } } //只能获取line 1 } = node -
对象属性重命名
const { log: minelog } = console minelog("hello") // hello 重命名后不能使用 log let obj = { age: 1 } let { ["age"]: age } = obj console.log(age)
字符串解构
字符串本身也是一个对象,有时候,可以当成一个数组解构
const [a, b, c, d, e] = "hello"
//a "h"
//b "e"
//c "l"
//d "l"
//e "o"
有时候,当做一个对象解构
let { length: len } = "hello"
len // 5
允许指定默认值
let [foo = true] = []
foo // true
let { name = "swr" } = {}
console.log(name) // 'swr'
let { ["age"]: nian = 0 } = {}
console.log(nian)
function f() {
console.log("aaa")
}
let [x = f()] = [1]
x 指定了默认值,但是 1 的解构赋值是成功的,所以并不会执行 f()。 当然,默认值也是可以使用变量的,但是这个变量必须是已经声明的。
注意
-
如果右边的值不是数组或者对象,会先将其转为对象,但是由于 null 和 undefined 无法转为对象,所以直接给 null 和 undefined 解构会报错:
var obj = undefined //或者是null const { a = 1 } = obj //Uncaught TypeError: Cannot read property 'a' of undefined/nullvar obj = { // sex: undefined/null } var { sex: { age } //obj中无sex属性或sex值为undefined/null时报错 } = obj // Uncaught TypeError: Cannot read properties of null (reading 'age') -
对象和数组解构赋值时,默认值生效的条件是: 严格等于(===)undefined
-
对象解构:
let { a = 1 } = { a: undefined } // a: 1 let { a = 1 } = { a: null } // a: null -
数组的解构:
let [a = 1] = [undefined] // a: 1 let [a = 1] = [null] // a: null
展开运算符
应用
-
数组解构赋值
const [arg, arg1, ...arg2] = ["a", "b", "c", "d"] console.log(arg) //a console.log(arg1) //b console.log(arg2) //["c", "d"]数组中按顺序匹配,将 'a'赋值给 arg, 'b'赋值给 arg1, 剩下的元素以数组形式赋值给展开运算符作用的变量。
-
对象的解构赋值
const { a, b, ...data } = { a: 111, b: 222, c: 333, d: 444 } console.log(a) //111 console.log(b) //222 console.log(data) //{c:333,d:444}对象中定义一个量 a,一个变量 b, 在被解构的对象中进行匹配,匹配到与变量名相同的 key 值,就把 value 赋值给该变量,匹配与顺序无关,剩下的键值对以对象形式赋值给展开运算符作用的变量。
-
数组合并
var a = [1, 2, 3] var b = [4, 5, 6] var c = [...a, ...b] console.log(c) //[1,2,3,4,5,6] -
对象合并
var obj1 = { name: "kerwin", age: 100 } var obj2 = { location: "dalian" } var obj = { ...obj1, ...obj2 } //如果属性值同名后面的对象的属性值会覆盖前面的 console.log(obj) //{name: "kerwin",age: 100,location: "dalian"} -
复制(浅拷贝)
var a = [1, 2, 3] var b = [...a] b[0] = "kerwin" -
函数参数个数不确定(Math)
var test = function (a, b, ...arr) { console.log(arr) //[3, 4, 5] } //必须在最后一个参数,箭头函数可用 test(1, 2, 3, 4, 5) -
伪数组转换
function test() { var arr = [...arguments] //Array.from(arguments) console.log(arr) } test(1, 2, 3, 4, 5) -
传参
var arr = [1, 2, 3] var test = function (a, b, c) { console.log(a, b, c) } test(...arr)
模块化语法
1.私密不漏 2.重名不怕 3.依赖不乱
<script src="js/A.js"></script>
<script src="js/B.js"></script>
<script src="js/C.js"></script>
<!-- 用这种方法,C.js使用A.js和B.js中的
函数的话,C.js导入必须写在下面 -->
一个模块(module)就是一个文件。一个脚本就是一个模块。
模块可以相互加载,并可以使用特殊的指令 export 和 import 来交换功能,从另一个模块调用一个模块的函数:
- export 关键字标记了可以从当前模块外部访问的变量和函数。
- import 关键字允许从其他模块导入功能。
import { Fun1, Fun2, Fun3 } from "file.js"
以上代码的实质是从 file 模块加载 3 个方法 Fun1、Fun2 和 Fun3,其他方法不加载。这种加载方式称为编译时加载,即 ES6 能在编译时就完成模块编译。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果想要外接获取模块内部的某个变量或方法,就必须使用 export 关键字输出该变量。
export 命令
// profile.js
export var firstName = "John"
export var lastName = "Jackson"
export var year = 1999
从 profile.js 文件中,输出了 3 个变量。
这种写法等价于:
var firstName = "John"
var lastName = "Jackson"
var year = 1999
export { firstName, lastName, year }
export 命令除了可以输出变量,也可以输出函数和类,写法相同。
export 输出的变量就是本来的名字,但是可以使用 as 关键字重命名。
function f1() {}
function f2() {}
export { f1 as Fun1, f2 as Fun2, f2 as Foo }
可以使用 as 关键字对同一个变量或方法重命名多次,使其在引入模块中,可以使用不同的名字进行引用。
export 语句输出的值是动态绑定的,绑定其所在的模块。
import 命令
使用 export 命令定义了模块的对外接口后,其他 JS 文件就可以通过 import 命令加载这个模块的接口。
// main.js
import { firstName, lastName, year } from "./profile.js"
import 命令接受一个对象,里面指定了要从其他模块导入的变量名。对象中的变量名必须和要加载的模块导出的接口变量名一致(如果接口没有使用 as 关键字,就使用原始变量名,如果使用 as 关键字,则使用重命名后的接口名)。
同理,如果要对引入的变量名进行重命名,可以在 import 命令中使用 as 关键字,将输入的变量重命名。
// main.js
import { firstName as surname } from "./profile.js"
上述写法中,需要书写每个接口的变量名,如果是要对整个模块进行整体加载,可以使用星号(*)指定一个对象,将输出模块的所有接口都加载到这个对象上。
// main.js
import * as person from "./profile.js"
import 命令具有提升效果,会提升到整个模块的顶部首先执行。
script 标签 type="module"
<script type="module" src="×××.js"></script>
- type="module"的 script 标签内部默认是严格模式
- type="module"的 script 标签内的变量,是这个 script 标签的私有变量
- type="module"的 script 标签内的 js 会延迟执行。
- type="module"的 script 标签通过 src 属性引入的 js 代码需要服务端支持 cors 跨域。
- type="module"的 script 标签只有在支持 es6 的浏览器中才执行。
index.html
<script type="module" src="./profile.js"></script>
<script type="module">
import { firstName, lastName, year } from "./profile.js"
console.log(year)
</script>
export default 命令
import 命令在加载变量名或函数名时,需要事先知道导出的模块的接口名,否则无法加载。可以使用 export default 命令指定模块的默认输出接口。
// profile.js
export default function () {
console.log("my name is John Jackson, I was born in 1999")
}
// main.js
import myName from "./profile.js"
myName() // "my name is John Jackson, I was born in 1999"
在 main.js 文件中,myName 指代的就是 profile 文件输出的默认接口。这意味着 import 命令可以用任意名称指向 profile 文件输出的默认接口,而不需要知道接口名。
一个模块只能有一个默认输出,因此 export default 在一个模块中只能使用一次。所以,对应的 import 命令可以不加大括号。
如果要在一条 import 命令中同时引入默认方法和其他变量,可以写成以下这样:
import customName, { otherMethod } from "./module-name.js"
customName 指代默认接口的命名,otherMethod 指代其他接口。
export from
可以在一个文件下将多个文件下的多个导出统一导出。
export * as shape from "./Shape.js"
//导出bar中的默认导出
export { default as function1, function2 } from "bar.js"
export * 表示输出模块 Shape 的所有属性和方法,但不会输出 Shape 的默认方法。
与之形成对比的是联合使用导入和导出:
import { default as function1, function2 } from "bar.js"
export { function1, function2 }
module 命令 如果要整体加载模块,可以使用 module 命令代替上述的 import * as 命令。module 命令不会加载模块的默认方法。需要额外使用 import 命令加载模块的默认方法。
// main.js
module person from './profile.js'
ES6 模块加载的实质
ES6 模块输出的是值的引用。
ES6 模块遇到模块加载命令 import 时不会去执行模块,只会生成一个动态的只读引用。等到真的需要用到时,再到模块中取值。
原始值变了,输入值也会跟着变。ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
// lib.js
export let count = 3
export function add() {
count++
}
// main.js
import { count, add } from "./lib"
console.log(count) // 3
add()
console.log(count) // 4
由于 ES6 输入的模块变量只是一个“符号链接”,所以这个变量是只读的,对它进行重新赋值会报 TypeError 异常。
// lib.js
export let obj = {}
// main.js
import { obj } from "./lib"
obj.prop = 123 // OK
obj = {} // TypeError
obj 指向的地址是只读的,无法为其重新赋值。