js的内容更多更杂,学习更多逻辑上的东西,难度也加大了许多,我将根据老师的笔记以及自己写的代码展开一下内容
数据类型
7 种原始类型
undefined, Boolean,Number, String,BigInt,Symbol, null
最后一种类型是: Object
## typeof,可以使用 `typeof` 来判断数据类型 如:
let obj = {}
console.log(typeof obj) // => 'object'
数据按照参数的传递方式,可以分为 `值传递类型` 和 `引用传递类型`
## 值类型
Boolean 布尔型,Number 数字型,String 字符串,BigInt 长整数
## 引用类型
Object 对象类型,symbol 符号类型
## 值类型和引用类型数据的区别
值类型:变量中直接存贮值本身
引用类型:变量中存储的是引用地址,而值是存在引用地址所指向的内存中的某个对应位置
## undefined 未定义
undefined 是一个单独的类型,用于给未定义的变量赋值
## null 空引用
null 值的是空引用,是 js 的一个原始数据类型,用来指代引用类型数据的空值
类型与类型之间可以相互转换,这里说两个比较特殊的转换序列化和反序列化
// 序列化就是将对象转换成字符串
// 反序列化就是将字符串转换成对象
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);
浏览器操作
## dom (document object model) 文档对象模型
dom 是 js 中的对象,用来操纵浏览器中的元素
## bom (browser object model) 浏览器对象模型
bom 是 js 中的对象,用来操纵浏览器,不同的 bom 对象 代表浏览器不同的功能
history: // 前进 // window.history.forward()
// 后退 // window.history.back()
location: // 跳转网页不计入历史 // window.location.replace('https://www.bilibili.com')
// 刷新页面// window.location.reload()
// 地址栏参数// location.search
localStorage:// localStorage 数据持久化到浏览器中,关闭窗口或浏览器,都不会消失
// 设置数据
// 第一个参数:key
// 第二个参数:value 且必须是字符串
// window.localStorage.setItem('name', '张三')
// 通过索引方式赋值 等价于 setItem
// localStorage['sex'] = 'male'
navigator:用于查看设备信息
运算符和条件判断
比较运算符:
// 大于 console.log(1 > 2);
// 小于 console.log(1 < 2);
// 大于等于 小于等于
console.log(3 >= 3)
console.log(3 <= 3)
// == 非严格相等判断
// 非严格相等判断 在运算符左右两边不需要使用相同的数据类型
// 当数据类型不同时 == 运算符将自动进行隐式转换
let a = ''
let 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);
赋值运算符
// += 自增
a += 2 // 等价于 a = a + 2
// -= 自减
a -= 5 // 等价于 a = a - 5
// *= 自乘
a *= -1
// /= 自除
a /= 2
// %= 自模
a = 5
a %= 3 // 等价于 a = a %
// ++ 自增
a = 0
a++
// -- 自减
a = 0
a--
// 自增自减运算符位置的区别
// 运算符放前面,则先运算后引用
console.log(--a);
// 运算符放后面,则先引用后运算a--
逻辑运算符
// && 与(短路运算)
// 语法:bool expression && bool expression
// 两个表达式都为真,结果返回真;任一一个表达式为假,结果返回假
// || 或(短路运算)
// 语法:bool expression || bool expression
// 两个表达式任一一个为真,结果返回真;否则为假
// !非 作用是取反
// 语法: !(tool express)
三目运算符
// 三元运算符: 用于判断一个表达式结果,为真时,返回结果1,为假时返回结果2
// 语法:bool expression? case1: case2
// 作用:主要用在给变量赋值
// 嵌套三元运算符 在问号或冒号处可以换行
console.log(obj.sex === 'male' ? '男' :
obj.sex === 'female' ? '女' : '其他');
// 当嵌套三元运算符在同一行内书写,请给嵌套的三元运算符的表达式用圆括号包起来
console.log(obj.sex === 'male' ? '男': (obj.sex === 'female' ? '女' : '其他'));
算数运算符
// 加减乘除符号
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);
// ** 幂运算 运算 n 的 m 次方是多少
console.log(2 ** 3);
// 算数运算符遵循数学的符号优先级
console.log(1 + 2 * 3 ** 2);
// 可以使用圆括号来分组
console.log(1 + (2 * 3) ** 2);
运算符的优先级
++ -- > 算术运算符 > 比较运算符 > 逻辑运算符 === 三元运算符 > 赋值运算符
条件判断
if
// if 语句:用于进行条件判断的语句
// if(bool expression) {
// // code block 代码块
// }
// 作用:当布尔表达式值为真时,执行后面的代码块
// if else: 如果。。。否则。。。
// 语法:
// if(bool expression) {
// // code block 代码块
// } else {
// // code block
// }
// if else if 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 else if else 语句,若代码块中只有一句话 则可以省略花括号
// 总结:
// 1. if 语句要么执行要么不执行
// 2. if else 语句,一定有一个会被执行,二选一
// 3. if else if 语句,多选一或都不执行
// 4. if else if else 语句,一定有一个会被执行,多选一
switch:
// 语法:判断value值为多少,如果为constant1则执行其后的代码,
// 如果为constant2则执行其后的代码,
// 如果不满足所有的case语句,则执行default
// switch ( value ) {
// case constant1:
// // code
// break;
// case constant2:
// break;
// ... 可以有任意多个case语句
// default: // default 不是必须要写的
// break;
// }
数组和循环
数组
// 存储一组数据的一个容器
// 声明数组
// 使用脚本形式声明数组
let arr = []
// 声明并初始化数组
// 数组中的每一个数据称为 数组成员
arr = [false, 2, 'hello world', { name: '张三' }, null, undefined]
// 对象形式的声明方法
arr = new Array()
// 声明并初始化
arr = new Array(false, 2, 'hello world', { name: '张三' }, null, undefined)
// 访问数组成员
// 使用索引值访问数组中的成员
// 索引:数组给每个成员的编号称为索引,索引值从零开始依次递增1
// 访问数组成员时,若索引超出数组范围,则获取到的结果为 undefined
console.log(arr['0']);
console.log(arr['1']);
// 数组索引可以不适用引号
console.log(arr[2]);
// 给数组成员赋值
// 使用数组变量名加上索引值进行赋值,可以给指定索引位置的成员进行赋值
arr[4] = { name: '李四' }
// 数组长度(指的就是数组成员的个数)
// 若赋值的时候索引若不是正整数,该值将被视为数组对象的属性值,不计入数组成员,所以不会影响数组长度
数组的操作
// push 追加数据到数组末尾
// 参数:被添加的新数组成员
arr.push('hello world')
// pop 从尾部取出一个成员
// 返回值是取出的成员
let r = arr.pop()
// unshift 在头部添加数据
// 参数:被添加的新数组成员
arr.unshift('my best day')
// shift 从头部取出一个成员
// 返回值是取出的成员
r = arr.shift()
// push 和 unshift 可以批量添加成员
arr.push('a', 'b', 'c')
arr.unshift('x', 'y', 'z')
// 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('-*-')
// includes 判断是否包含某成员
r = arr.includes('z')
// indexOf 查询指定数组成员的索引
r = arr.indexOf('b')
console.log(r);
// 可以使用indexOf判断是否包含某个数组成员 若不包含 返回 -1
r = arr.indexOf('g')
console.log(r); // => -1
// slice 数组切片 获取子数组
// 切片遵循“前截后不截”原理: 起始位置包含在结果内,结束位置不包含
r = arr.slice(5, 8)
console.log(r);
// 参数只有一个,代表从该位置开始 一直截取到最后
r = arr.slice(5)
// Array.isArray 方法检测变量是否为数组
// 是数组的话返回 true 否则为 false
多维数组
// 多维数组:多个维度(rank)组成的数组
let arr = [1, 2, 3]
// 声明二维数组
// 二维数组中每个一维数组的成员都是一个数组
arr = [[1, 2], [3, 4], [5, 6]]
// 读取数组
// 可以将二维数组视为一个x轴和y轴构成的二维平面,所以任意值都可以由x,y坐标来表示
console.log(arr[0][1]);
console.log(arr[2][0]);
// 声明多维数组
// 多维数组是数组内嵌套多层数组的数组
arr = [[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]
// 访问数组
// 可以理解成三维空间中访问一个三维坐标系
console.log(arr[0][1][1]);
循环
// 循环就是重复执行某一段代码的行为
// for 循环
// 例如按次数进行循环
let count = 5 // 循环次数
// for (let i = 0; i < count; i++) {
// console.log('hello world');
// }
// 语法:
// for( 初始化语句; 循环条件; code block 运行一次结束后运行的代码 ) { // code block }
// 初始化语句:初始化变量值
// 循环条件:循环条件是个布尔表达式,若结果为真,则执行 code block 的代码块
break:跳出循环 continue继续循环
// do while 循环:先执行循环代码,再判断循环条件
// 语法:
// do {
// code block
// } while (condition)
// while 循环:当。。。时,就循环执行代码
// 语法:
// condition: while循环的条件,是一个布尔表达式,若成立则执行循环的 code block
// while (condition) {
// code block
// }
函数和数组的遍历函数
数组的迭代操作
// 以下所有的遍历函数的参数都是相同的回调函数
// 回调函数接收以下参数
// el 被遍历的当前数组成员
// index 被遍历的当前成员的索引
// arr 被遍历的数组对象
// forEach 循环遍历每一个数组成员
// every 和 forEach 一样遍历每个数组成员,但是中途可以跳出循环
// map 映射数组到新数组中
// map 的回调函数将返回一个值,代表当前被遍历的数组成员在新数组中的投影
// map 函数将返回一个新的投影数组
// 例如将students中所有的年龄放入一个新数组
// let r = students.map((el, index, arr) => {
// // return 的内容将被放到新的数组中
// return el.age
// })
// filter 过滤器
// filter 返回过滤完后的新数组
// let r = students.filter((el, index, arr) => {
// // 过滤掉年龄大于25岁的成员
// // if (el.age > 25) {
// // // return false 代表过滤掉该数据
// // return false
// // }
// // return true // return true 代表保留该数据
// return el.age <= 25
// })
// find 查找符合回调函数条件的数组成员并返回它
// 返回值为查询结果
let r = students.find((el, index, arr) => {
// 查找女同学
if (el.sex === 'female') {
return true // return true 代表当前成员就是要查找的元素
}
return false
return el.name === '隔壁老王'
})
// findIndex 查找对应成员所在索引
// 使用方法和 find 相同,返回结果为查找到的成员索引
// let r = students.findIndex((el, index, arr) => {
// return el.name === '隔壁老王'
// })
// some 方法测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个Boolean类型的值。
// 给出一个判断条件,some 函数将对每个成员都做出相同的判断
// 任意成员符合条件返回 true 时 some函数则返回true 否则为 false
// 请判断 arr2 中是否有成员存在于 arr1 中
let arr1 = ['x', 'y', 'z']
let arr2 = ['a', 'b', 'z']
// let r = arr2.some((el, index, arr) => {
// // 判断当前数组成员是否再 arr1 中存在
// if (arr1.includes(el)) {
// // 返回 true 代表 找到一个满足条件的数组成员
// return true
// }
// return false
// })
// sort 参数是一个排序规则的函数
// el1 和 el2 代表的是排序时两两比较的两个数组成员
// 排序原理是,将每个成员都和剩下所有成员进行比较,每一对 el1 和 el2 决定他们的先后顺序
// 也叫“冒泡排序”
// sort 函数执行完后将返回新数组
students.sort((el1, el2) => {
if (el1.age > el2.age) {
// el1.age 若大于 el2.age 则 若从小到达排列 el1 应该放到 el2 的右侧
// 右侧联想 右箭头 >;即大于符号,则此处应该返回一个大于零的值
return 1
} else if (el1.age < el2.age) {
// el1.age 若小于 el2.age 则 若从小到大排列 el1 应该放到 el2 的左侧
// 左侧联想到 左箭头 < ;即小于符号,所以返回一个小于 0 的值
return -1
} else {
// 若 el1 和 el2 不需要交换顺序 则返回 0
return 0
}
})
mapReduce统计模型
// reduce 中的参数
// p: 第一个数组成员或上一次运行回调函数的返回值
// n: 下一个被遍历的成员
reduce((p,n)=>{
return p+n //求和
})
字符串操作
正则表达式
// \ 斜杠:转义
console.log(/\\abc/.test('\\abc'));
// ^ :匹配字符串的开头 regex = /^abc/
// $ :匹配字符串的结尾 regex = /xyz$/
// ------------------- 匹配字符个数的符号
// 这些匹配字符个数的符号,代表的意思是:匹配前一个字符多少次
// * :匹配任意次 regex = /^o*k$/
// ? : 匹配0次或1次 regex = /^o?k$/
// + : 匹配至少1次 regex = /^o+k$/
// {n} : 匹配指定次数 regex = /^o{2}k$/
// {n,} : 匹配至少n次 regex = /^o{2,}k$/
// {n,m} : 匹配至少n次,至多m次 regex = /^o{1,3}k$/
// ------------------- 匹配字符个数的符号 - end
// [xyz]: 匹配字符集合,匹配一个字符,该字符在方括号内 regex = /^[xyz]$/
// x|y : 或 regex = /^(good|bad)$/
// [^xyz]: 匹配负值集合,匹配一个字符,该字符不在方括号内 regex = /^[^xyz]$/
// [a-z] [0-9] : 取范围值,匹配一个字符,该字符在指定范围内 regex = /^[A-Z][0-9]$/
// [^5-7]: 取范围负值,匹配一个字符,该字符不在指定范围内 regex = /^[^5-7]$/
// ------------------- 分组 (pattern)
// (pattern): 将pattern里面的所有字符当作一个字符处理 regex = /^abc(123)+xyz$/
// 站在字符串的角度看,圆括号不仅有分组的作用,同时,它将被取值
// regex = /abc(123)+xyz/
regex = /abc123xyz/
// (?:pattern): 匹配分组内容,但不获取圆括号中的值
regex = /abc(?:123)+xyz/
r = '000123abc123123xyz444555'.match(regex)
// assert 断言 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions/Assertions
// 什么是断言? 断言就是一段下笃定的言论,例如: 今天一定会下雨
// 先行断言
// 语法: 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(/(?<!小红)小明/));
// 不开全局模式 只会匹配到第一个符合条件的字符串
// 全局模式的另一个应用场景
// 字符串的替换函数
str = '共产党万岁,共产党伟大,共产党真牛逼'
regex = /共产党/
// replace 用于替换字符串
// 第一个参数: 要替换的内容,是一个正则表达式
// 第二个参数: 要换入的内容
// 返回值: 替换后的字符串
r = str.replace(regex, '***');
console.log(r);
// 使用全局模式进行全局替换 否则只会替换第一个
regex = /共产党/g
r = str.replace(regex, '***');
console.log(r);
// match:从字符串中匹配出符合正则的字符串
// 参数:用于匹配的正则表达式
// 返回值:包含了匹配结果的数组
闭包和计时器
闭包
// 闭包也叫函数闭包,通过函数产生一个封闭的内存空间,包裹一些需要被保存的数据
// 且函数需要返回一个持续引用的对象,这就叫闭包
// 为了方便理解,做如下的假设:
// 1. 假设有一个函数A
// 2. A内声明变量B
// 3. A返回一个 包含函数的内容
// 4. A返回的 包含的函数必须引用变量B
// 5. 此时函数A就是闭包的
// 应用场景
// 闭包用于存储一些不让函数外访问的数据,或者为了避免作用域中变量名的冲突,可以使用闭包
计时器
当经过指定时间后触发一段代码的函数就是一个计时器
// 声明一个计时器:setTimeout
// 第一个参数:计时器计时结束后触发的函数
// 第二个参数:计时时长,单位:毫秒
// 返回值: 计时器id
clearTimeout(timerId)
// 计时器id 用于停止计时
// setInterval 循环计时函数
// setInterval 函数,每次经过指定时间,触发一次指定的函数
clearInterval(timerId2)停止计时
// 参数和返回值 与 setTimeout 相同
时间对象和数学函数
时间对象
// 创建时间对象的方法
// 语法:new Date(params)
// 如何读取时间?如何设置时间?
// 读取时间
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()) // 毫秒
设置用set
数学函数
// 三角函数
// 需要注意的是,三角函数 sin()、cos()、tan()、asin()、acos()、atan() 和 atan2() 返回的值是弧度而非角度。
// 若要转换,弧度除以 (Math.PI / 180) 即可转换为角度,同理,角度乘以这个数则能转换为弧度。
事件
// 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('获取焦点');
})
// blur 失去焦点
input.addEventListener('blur', () => {
console.log('失去焦点');
})
box.addEventListener('focus', () => {
console.log('box获取焦点');
})
box.addEventListener('blur', () => {
console.log('box失去焦点');
})
// 鼠标事件
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')
// 变化事件
// 一般除了输入框外都使用 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);
})
冒泡,捕获事件
在html中触发事件的元素,将会把事件不断的向父元素传递,这个过程叫做事件冒泡
阻止事件冒泡
document.querySelector('button').addEventListener('click', (ev) => {
// 使用事件对象的 stopPropagation 来阻止事件冒泡
// ev.stopPropagation()
console.log('button');
})
// 事件触发后,可以由上级元素先处理事件,这个过程就是捕获事件
// addEventListener 的第三个参数代表是否捕获事件
// 捕获事件的元素会先处理事件,然后将事件对象还给产生事件的元素,然后正常冒泡
document.body.addEventListener('click', () => {
console.log('body');
}, true)
面向对象编程
js用属性和行为来描述某个物体而产生的一种数据结构,该数据称为对象
// 声明对象
// 设计一个对象最重要的就是设计它的属性和行为
// 属性: 都是一些值
// 行为: 都是一些函数
let duck = {
// 设计对象属性如下
color: '#0f0',
age: 2,
name: '押宝',
// 对象行为
// 对象的行为一般称为方法
swim() {
console.log(`鸭子在游泳`);
},
shout() {
console.log('叫啦');
},
fly() {
console.log('鸭在飞');
}
}
类和实例对象
<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
}
// 行为
run(){
console.log(`价值 ${this.value} 的 ${this.name} 在跑`);
}
}
// 实例化类型
// 生成一个类型的个例的过程称为实例化
// 以下代码中 new Human() 这句话就是在实例化Human类
// 实例化的结果被存在了 zhangSan 变量中,
// 我们称实例化的结果为:实例,实例对象,类实例
// new 关键字用于实例化类型
// new 关键字后面的 Human() 实际上是在调用构造函数
let zhangSan = new Human('张三', 17, 'male')
console.log(zhangSan);
// 修改实例属性
zhangSan.age = 24
// 调用实例行为
zhangSan.eat('小黄瓜')
let car = new Car(10000000, '陆厂', '跑得快')
console.log(car);
console.log(car.value);
car.run()
// 实例和类的关系是:
// 类是实例的模板,实例是类的成品
// 访问实例属性和行为
// 所有使用 constructor 构造函数构造而成的数据类型都是 Object 对象类型(也就是使用关键字 new 创建的对象)
// 例如:
// console.log(typeof Number(123))
// console.log(typeof new Number(123))
// console.log(typeof String('hello'))
// console.log(typeof new String('hello'))
// 这可以解释为什么数组是 object 类型
// 因为数组可以使用 new Array() 来创建
</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);
fn()
}
// 私有方法
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
}
run() {
console.log(`价值 ${this.value} 的 ${this.name} 在跑`);
console.log(this.#engine);
this.#error()
}
#error() {
console.error('这是私有的方法');
}
}
let zs = new Human('张三', 17, 'male')
console.log(zs.hobbie);
// zs.fn()
let car = new Car(1000, 'ok', 'hao')
// console.log(car.#engine);
car.run()
// car.#error()
</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')
// 调用静态属性或方法,使用类名调用而不是实例对象
console.log(Human.category);
Human.greedy()
面向对象编程更难转变的是思维方式
this关键字和继承
// js 执行上下文中的 this
console.log(this); // => window
console.log(this === window);
// 函数内的this
function fn() {
// 非严格模式下 函数内 this => window
console.log(this);
}
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可能被其他函数赋值,所以this不是undefined
// 此处 事件回调函数被 addEventListener 赋值了其中的 this
// 所以这里的this指的是绑定事件的那个dom对象
document.querySelector('button').addEventListener('click', function () {
console.log(this);
})
严格模式下
// 1. js 执行上下文 => window
// 2. 只要是函数 this 就是 undefined (函数=>undefined)
// 3. 方法内的this指向调用方法的对象 (方法=>实例)
// 4. this 可能被其他函数赋值 例如 addEventListener 这种时候 this 既不是 window 也不是 undefined,它取决于函数 addEventListener 自己
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>
// 子类构造函数中理论上都应该使用 super 关键字来构造父类
// 在构造函数中,super 代表父类的构造函数
super(name)
// 子类构造函数要使用 this 关键字的话,则必须先调用 super
lambda中的this
// 1. lambda 表达式没有 this
// 2. lambda 表达式中的 this 来自于声明函数时上下文中的 this
继承
// 从父类传承下他的属性和行为,使子类具备相同的属性和行为,同时子类可以拥有自己的属性和行为
// 父类(parent class)也叫做基类(base class),也叫超类(super class)
extends
<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()
console.log(pk);
// 原型 __proto__ 和 prototype 的区别
// __proto__ 实例对象中访问原型的属性
// prototype 类访问原型的属性
// 实例对象才能访问 __proto__ 对象
console.log(pk.__proto__);
// 类名才能访问 prototype
console.log(PickCar.prototype);
// 原型属性或原型函数
// 例如
// Car.prototype.wheelCount = 4
// Car.prototype.shout = () => {
// console.log('喇叭叫')
// }
// // 原型属性或原型函数可以在类实例或子类的实例中调用
// // 实例化对象会继承原型属性和函数
// let c = new Car()
// console.log(c.wheelCount);
// c.shout()
// pk = new PickCar()
// console.log(pk.wheelCount);
// pk.shout()
// // 若不实例化类,可以通过原型,直接访问,例如:
// console.log(Car.prototype.wheelCount)
// Car.prototype.shout()
</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>
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>
包装类
<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>
delete
// 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);
访问器get和set
访问属性用的函数,访问有两重含义,读值和赋值
// 作用:可以控制读取属性的结果,可以控制赋值属性的内容(控制访问)
// 具体的作用
// getter:
// 1. 格式化数据显示
// 2. 简化路径访问
// setter:
// 1. 验证赋值参数的合法性
<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>
ES6语法
<script>
// let 关键字
let a = 11
// const 定义常量 常量是readonly只读的属性
// 一旦初始化后 就不能修改其值
const c = 15
console.log(c)
const g = 9.8
// ... 扩展
let obj = { x: 1, y: 2 }
// 复制对象
console.log({ z: 3, ...obj });
let arr = [1, 2, 3]
// 复制数组
console.log([...arr, 4]);
// rest parameters
function fn(a, b, ...c) {
console.log(a);
console.log(b);
console.log(c);
}
fn(1, 2, 3, 4, 5, 6)
// lamda 表达式 也称 箭头函数
let fn2 = () => ({})
// 模板字符串
let year = 2020
let month = 8
let day = 19
console.log(`${year}/${month}/${day}`);
// 解构复制
// 对象
let obj2 = { aa: 1, b: 2 }
let { aa, b } = obj2
console.log(aa, b);
// 数组
let arr2 = [1, 2, 3]
let [x, y, z] = arr2
console.log(x, y, z);
// 函数参数的解构
function fn3(a, { b, c }) {
console.log(a);
console.log(b);
console.log(c);
}
fn3('hello', { b: 2, c: 5, a: 16 })
// Promise语法
let promise = new Promise((resolve, reject) => {
})
// 语法
// Promise.resolve(value);
// Promise.reject(value);
// 参数
// value
// 将被Promise对象解析的参数,也可以是一个Promise对象,或者是一个thenable。
// 将不是thenable的对象转换为thenable的对象
Promise.resolve()
// Promise.all()
// Promise.race()
// 定义对象属性和方法的简写
let p = 'hello'
let obj5 = {
// 传统定义方法,需要声明key和value
// p: p
// 但当 key 和 value 的名称相同时,可以省略冒号后的内容
p,
// 传统定义函数
// fn: function(){}
// 可以简写成
fn() { }
}
// ? 问好语法
// 判断是否存在,存在就执行,否则不执行
let o = { a: 1, fn() { console.log('hello') } }
console.log(o?.a);
o?.fn()
o = undefined
o?.fn()
</script>
Map和Set
<script>
// Map 的使用类似 json 对象
// map 实际上就是 key: value 键值对
// 声明 Map
let map = new Map([
['a', true],
['b', 123],
['c', 'string']
])
console.log(map);
// 读值
console.log(map.get('a'));
console.log(map.get('b'));
console.log(map.get('c'));
console.log(map.get('d')); // => undefined 不存在的key读出来是 undefined
// 赋值
map.set('d', { name: '张三' })
map.set('e', function () { console.log('hello') })
console.log(map);
// 迭代map
let keys = map.keys()
console.log(keys);
let key
// keys.next() 方法的作用是迭代下一个成员 返回一个对象
// 返回对象包括 value 属性和 done 属性 done 代表是否迭代完成
while (!(key = keys.next()).done) {
// value 就是每一个key
console.log(key.value);
console.log(map.get(key.value));
}
console.log('=====================================================');
// 可以使用 forEach 迭代
map.forEach((value, key, _map) => {
// value map对应key 的值
// key 迭代的键
console.log(value);
console.log(key);
// _map 当前正在迭代的map
console.log(_map);
})
// has 判断是否存在某个 key
console.log(map.has('d'));
console.log(map.has('f'));
// for of 遍历 Map
for (const entry of map) {
// entry 是包含了一组 key value 的数组
console.log(entry[0]); // => 0 号成员就是key
console.log(entry[1]); // => 1 号成员就是value
}
// Set 是不重复的数据集,若存入多个重复数据会自动去重
let set = new Set([1, 1, 2, 2, 3, 3])
console.log(set);
// 添加
set.add(true)
set.add(12)
set.add('hello')
let o = { name: '李四' }
set.add(o)
console.log(set);
// 删除
set.delete('hello')
set.delete(o)
console.log(set);
// 判断是否存在某个值
console.log(set.has(12));
console.log(set.has('world'));
// 迭代 set
keys = set.keys()
while (!(key = keys.next()).done) {
console.log(key.value);
}
// foreach 迭代 set
set.forEach((value1, value2, _set) => {
console.log(value1);
console.log(value2);
console.log(_set);
})
// 通过 set 转换成数组
// 可以通过这种方法来让数组去重
let arr = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
console.log(Array.from(new Set(arr)));
// for of 遍历 Set
for (const value of set) {
console.log(value);
}
// map 和 set 的size属性代表数据长度
console.log(set);
console.log(map);
console.log(set.size);
console.log(map.size);
// 但凡包含迭代器的对象都可以使用 forof 来迭代
</script>