JS基础
JavaScript是什么:一种运行在客户端的语言,实现人机交互效果。
作用:
- 网页特效
- 表单验证
- 数据交互
- 服务端编程(Node.js)
JavaScript组成:ECMAScript、Web APIs
- ECMAScript规定了js基础语法核心知识。
- Web APIs包括DOM和BOM。DOM用于操作文档,比如对页面元素进行移动、缩放、增删等操作;BOM操作浏览器,比如页面弹窗,检测窗口宽度,存储数据到浏览器。
JavaScript书写位置:与CSS相似,包含行内JavaScript、内部JavaScript、外部JavaScript。
- 内部JavaScript:直接写在html中,用script标签包裹。script标签规范应写在</body>上面。因为浏览器会按照代码顺序进行加载,如果先加载JavaScript,那么代码可能因为需要修改的元素尚未加载而失效。
- 外部JavaScript:写在.js文件中,添加script标签并设置src属性引入。外部引用使代码有序易于复用。
- 内联JavaScript:写在标签内部。vue框架会使用该模式。
注释规则与C语言相同。
JavaScript语句可以不以分号结束,也可以以分号结束,实际使用时统一风格即可。
JavaScript的输入输出语法
输出
- document.write() 向body输出内容
- alert() 页面弹出通知框
- console.log() 程序猿调试
输入
- prompt() 显示对话框提示用户输入信息。
其中alert和prompt会跳过页面渲染先被执行。
基础知识点查询JavaScript | MDN
杂碎知识点
检测关键字类型:typeof x 或 typeof(x)
隐式转换:
- + 号两边有一个是字符串,另外一个也会转成字符串。
- 其他运算符都会吧数据转成数值类型。
- 小技巧:+号作为正好解析可以转成数字型;任何数据与字符串相加为字符串。
显式转换
- Number() 转成数字型,有非数字内容转换失败为NaN,代表非数字。
- parseInt() 只保留整数
- parseFloat() 可以保留小数
- String() x.toString() 转换成字符串
比较运算符
- ==:左右两边值是否相等
- ===:左右两边值和类型是否都相等
- !==:左右两边是否不全等
NaN不等于任何值,包括其本身。
开发中更喜欢===和!==。
数组声明
let arr = new Array(,,,) 和C语言形式。
数组函数
- arr.push(,,,)将一个或多个元素添加到原数组尾部,返回新数组长度。
- arr.unshift(,,,)将一个或多个元素添加到原数组开头,返回新数组长度。
- arr.pop()删除数组最后一个元素并返回该元素。
- arr.shift()删除第一个元素并返回该元素。
- arr.splice(start,deleteCount)删除指定元素。
函数声明 function name(){}
形参默认值 function name(x=0){}
匿名函数/函数表达式 let fn = function(){}
调用 fn()
对象声明 let name = {} 或 let name = new Object()
对象属性
let obj = {
age: 18,
gender: '女',
name: funtion(){}
}
- 属性名可使用引号括起,一般情况省略。
- 属性删除 delete obj.x
- 多次属性或含-的属性不能用点操作符访问,要用中括号访问 obj['name'] 不加引号当变量处理。
- 遍历对象的属性 for(let a in obj){a.attr}
getter 将对象属性绑定到查询该属性时将被调用的函数。
const obj = {
log: ['a', 'b', 'c'],
get latest() {
return this.log[this.log.length - 1];
},
};
console.log(obj.latest);
// Expected output: "c"
JS Web
变量声明const优于let,因为很多对象是不会更改的。
JavaScript web API分为DOM(文档对象类型)和BOM(浏览器对象类型)
DOM树:将HTML文件以树状结构表现出来,称之文档树或DOM树。
DOM对象:浏览器根据html标签生成的JS对象。
DOM的核心思想就是把网页内容当做对象处理。
document对象是DOM里提供的对象,包含了所有网页内容。
根据CSS选择器来获取DOM元素
选择匹配的第一个元素 document.querySelector('css选择器')
- 参数为一个或多个有效的CSS选择器字符串。
- 返回值为匹配的第一个元素,一个HTMLElement对象。无匹配返回null。
选择匹配的多个元素 document.querySelectorAll('css选择器')
- 参数为一个或多个有效的CSS选择器字符串。
- 返回值为CSS选择器匹配的NodeList 对象集合。
- 该集合可以用下标访问但是无法使用数组函数。
其他获取元素方式
- document.getElementById()
- document.getElementByTagName()
- document.getElementByClassName()
操作元素内容
想修改标签里面的元素内容,可以使用以下几种方式:
- .innerText属性 解析纯文本,不解析标签。
- .innerHTML属性 会解析标签,多标签使用模板字符。
操作元素属性
- 操作标签元素属性:如href title src
- 操作元素样式属性:style className classList
style 对象.style.样式属性 = 值(如果属性有-连接符,要使用小驼峰命名法,如marginTop backgroundColor)
className 元素.className = 'active' (active是类名)如果修改样式较多,可以通过借助css类名方式(直接使用className会覆盖之前的类名)
classList 元素.classList.add('类名')/remove('')/toggle('') 追加/删除/切换一个类名。为了解决className容易覆盖的问题,可以用className追加和删除类名。
修改表单属性 表单.value( = newValue) 表单属性一律用布尔值表示,true代表有效果,如disabled、checked、selected
自定义属性(HTML5)以data-开头,以dataset对象方式获取。
如:
<p data-id='10'></p>
console.log(p.dataset.id)
定时器-间歇函数
每隔一段时间自动执行一段代码。
- 开启定时器 setInterval(函数, 间隔时间) 每隔一段时间调用此函数,时间单位为毫秒。
- 关闭定时器 let x = setInterval(函数, 间隔时间); clearInerval(x);
使用场景:隐私协议、轮播图。
事件基础
事件监听
元素.addEventListener('事件类型', 要执行的函数)
使用场景:点击隐藏或显示元素:display:none/display:block
其他版本:元素.on事件 = function(){}
区别:on方式会被覆盖,addEventListener可绑定多次,推荐。
事件类型
鼠标事件
- click 鼠标点击
- mouseenter鼠标经过
- mouseleave鼠标离开
焦点事件
- focus获得焦点
- blur失去焦点
键盘事件
- Keydown键盘按下触发
- Keyup键盘抬起触发
文本事件
- input用户输入事件
事件对象
包含了事件出发时的相关信息,可以判断用户的操作做出对应的响应。
事件对象的获取
事件绑定的回调函数的第一个参数就是事件对象,一般命名为event、ev、e
元素.addEventListener('click',function(e){})
事件对象的常用属性
- type 当前事件类型
- clientX/clientY 获取鼠标相对于浏览器可见窗口左上角的位置
- offsetX/offsetY 获取光标相对于当前元素左上角的位置
- key 用户按下的键盘键的值(不提倡使用keyCode)
应用:评论回车发布
环境对象
目标:分析判断函数运行在不同环境中this所指代的对象。
环境对象:指函数内部特殊变量this,代表当前函数运行时所处的环境。
- 函数调用方式不用,this指代对象也不同
- 谁调用this就是谁
- 直接调用函数,this指代window。
回调函数
一个函数被当作参数传递给另一个函数时,这个函数就是回调函数。
匿名函数作为回调函数比较常见。
事件流
事件流指事件完整执行过程中的流动路径。事件触发时,会经历两个阶段:捕获阶段(从父到子)、冒泡阶段(从子到父)。实际工作中以事件冒泡为主。
事件捕获:从DOM的根元素开始执行对应事件(从外到里)。
addEventListener的第三个参数传入true代表捕获阶段触发(很少使用),默认false为冒泡阶段触发。L0事件监听只有冒泡阶段。
事件冒泡:一个元素的事件被触发,同样的事件将会在该元素的所有祖先元素中依次触发,这一过程被成为事件冒泡。事件冒泡默认存在。
阻止冒泡:把事件限制在当前元素内,不影响祖先元素。
语法:事件对象.stopPropagation()
例子:
son.addEventListener('click', function(e){
alert('我是儿子');
e.stopPropagation();
})
此方法可阻断事件流动传播,在捕获阶段也有效。
某些情况要阻止默认行为的发生,如阻止链接跳转,表单域跳转。
语法:e.preventDefault()
const form = document.querySelector('form')
form.addEventListener('click', function(e){
// 组织默认提交行为
e.preventDefault();
})
解绑事件
on事件,用null覆盖就可以解绑
btn.onclick = function(){}
btn.onclick = null
addEventListener事件,需使用removeEventListener(匿名函数无法被解绑)
function fn(){}
btn.addEventListener('click', fn)
btn.removeEventListener('click', fn)
mouseover和mouseout会有冒泡效果,mouseenter、mouseleave没有冒泡效果(推荐)。
事件委托
优点是减少注册次数,提高程序性能。其利用了事件冒泡的特点,给父元素注册事件,当触发子元素时,会冒泡到父元素,触发父元素事件。
实现:.target.tagName可以获得真正触发事件的子元素。
const ul = document.querySelector('ul')
ul.addEventListener('click', function(e){
if (e.target.tagname === 'LI') {
this.style.color = 'pink'
}
})
HTML 文档中,tagName会返回其大写形式,所以是LI而不是li。
其他事件
页面加载事件:加载外部资源(图片,外联CSS、JS)加载完毕时触发的事件。
监听页面所有资源加载完毕:
window.addEventListener('load', function(){})
不光可以针对整个页面,也可以针对某个资源绑定load事件。
当初始的HTML文档被完全加载和解析后,DOMContentLoaded事件被触发,而无需等待样式表、图像等完全加载。
document.addEventListener('DOMContentLoaded', function(){})
页面滚动事件
很多网页需要检测用户滚动到某些区域后做一些处理,比如固定导航栏,比如返回顶部。
监听页面滚动:给window或document添加scroll事件
window.addEventListener('scroll', function(){})
监听某元素滚动直接给该元素添加即可。
获取位置
scrollLeft和scrollTop
- 获取被卷进去的大小
- 获取元素往左网上滚出去看不见的距离
- 可读写
- 尽量在scroll事件里获取被卷去的距离。
window.addEventListener('scroll', function(){
console.log(document.documentElement.scrollTop)
})
滚动到指定坐标 element.scrollTo(x, y)
页面尺寸事件
在窗口尺寸改变时触发事件
window.addEventListener('resize', function(){
// 检测屏幕宽度
console.log(document.documentElement.clientWidth)
})
获取元素可见部分宽高(不包含边框、margin、滚动条)clientWidth和clientHeight
- 元素尺寸与位置
- 获取元素宽高:offsetWidth和offsetHeight
- 包含自身设置的宽高、padding、border
- 获取值为数值,获取为可视宽高,如果元素被隐藏,获取结果是0.
获取位置:offsetLeft offsetTop
获取元素距离自己定位父级元素的左、上距离
DOM节点
日期对象
实例化
const date = new Date() // 获得当前时间
const date = new Date('2008-8-8') // 获得指定时间
日期对象方法
- getFullYear() 获得四位年份
- getMonth() 获得月份,0~11
- getDate() 获取月份中的每一天
- getDay() 获取星期,0~6
- getHours() 获取小时,0~23
- getMinutes() 获取分钟,0~59
- getSeconds() 获取秒,0~59
时间戳
1970/01/01 00:00:00 到现在的毫秒数
获取时间戳
// 1.
new Date().getTime()
// 2.
console.log(+new Date())
// 3.无需实例化,但是只能得到当前时间戳
console.log(Date.now())
节点操作
查找节点
- 子元素.parentNode 返回最近一级父节点,找不到返回null
- 父元素.childNodes 获得所有子节点,包括文本节点、注释节点
- 父元素.children 仅获得所有元素节点,返回集合
- 兄弟元素.nextElementSibling 获得下一个兄弟节点
- 兄弟元素.previousElementSibling 获得上一个兄弟节点
增加节点
newNode = document.createElement('标签名') // 创建节点
father.appendChild(newNode) // 追加节点
father.appendChild(newNode, target) // 插入到某元素前
节点.cloneNode(布尔值) 克隆出跟原标签一样的节点,布尔值为true会包含后代节点,默认false
删除节点
在JavaScript原生DOM中,删除元素必须通过父元素删除。
父元素.removeChild(abandoned)
M端事件
触屏/触摸事件touch
可响应用户手指/触控笔对屏幕或触控板的操作。
- touchstart 手指触摸到一个DOM元素
- touchmove 手指在一个DOM元素上滑动
- touchend 手指移开DOM元素触发
JS插件
- 官网 https://www.swiper.com.cn
- 在线演示 https://www.swiper.com.cn/demo/index.html
- 使用流程 https://www.swiper.com.cn/usage/index.html
- API文档 https://www.swiper.com.cn/api/index.html
BOM操作
Window对象
- BOM是浏览器对象模型。
- window对象是一个全局对象,也可以说是JavaScript中的顶级对象。
- document、alert()、console.log()等都是window的属性,基本的BOM属性和方法都是window的。
- 所有var定义在全局作用域中的变量、函数都会变成window对象的属性和方法。
- window对象下的属性和方法调用的时候可以省略window。
定时器-延时函数
JavaScript内置的代码延迟执行的函数setTimeout
setTimeout(回调函数, 等待的毫秒数)
清除延时函数:
let timer = setTimeout(回调函数, 等待毫秒数)
clearTimeout(timer)
延时器需要等待,所以后面的代码先执行。与间歇函数的区别是延时函数只执行一次。
JS执行机制
单线程,所有任务都要排队,这是处理用户交互及操作DOM的特性决定的。这意味着所有任务都要排队,如果JS执行时间过长,会造成页面渲染不连贯,导致页面加载阻塞。
为利用多核计算能力,HTML5提出Web Worker标准,允许JS脚本创建多个线程于是就有同步和异步。
- 同步:前一个任务结束后执行后一个任务,程序的执行顺序与任务的排列是一致的、同步的。
- 异步:在做一件事的时候可以做另一件事。
JS的异步是通过回调函数实现的。
异步任务的三种类型:
- 普通事件,如click、resize等
- 资源加载,如load、error等
- 定时器,包括setInterval、setTimeout等
异步任务添加到任务队列(也称消息队列)中。
JS先执行执行栈中的同步任务,异步任务放入任务队列中。一旦执行栈中所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行。
location对象
location数据类型是对象,它拆分并保存了URL地址的各个组成部分。
常用属性和方法:
- href 属性获取完整的URL地址,对其赋值时用于地址跳转
- search 属性获取地址中携带的参数,符号?后面部分
- hash 属性获取地址中的哈希值,符号#后面部分(后期vue路由的铺垫,不刷新页面显示不同页面)
- reload 方法用来刷新当前页面,传入参数true强制刷新
navigator对象
记录浏览器自身的相关信息
- userAgent 检测浏览器的版本及平台
history对象
管理历史记录,该对象与浏览器地址栏的操作
本地存储
经常需要本地存储大量数据,避免页面一刷就没,HTML5规范提出了相关解决方案。
- 数据存储在用户浏览器中
- 设置、读取方便、甚至页面刷新不丢失数据
- 容量较大,sessionStorage和localStorage约5M左右
localStorage 将数据永久存储在本地(用户电脑)
- 同一浏览器可以多页面共享
- 以键值对的形式存储
语法
- 存储数据 localStorage.setItem(key, value)
- 获取数据 localStorage.getItem(key)
- 删除数据 localStorage.removeItem(key)
sessionStorage
- 生命周期为关闭浏览器窗口
- 在同一窗口下数据可以共享
- 以键值对存储
- 用法与localStorage基本相同
存储复杂数据类型
需要将复杂数据类型转换成JSON字符串,并存储到本地。
语法:JSON.stringify(复杂数据类型)
const goods = {
name: '小米10',
price: 1999
}
localStorage.setItem('goods', JSON.stringify(goods))
从本地取出时,取出的是一个字符串,不能直接使用,需要通过JSON.parse(JSON字符串)转换成对象。
const obj = JSON.parse(localStorage.getItem('goods'))
console.log(obj)
正则
定义正则表达式
const 变量名 = /表达式/ 例如 const reg = /前端/
判断是否有符合规则的字符串
regObj.test(被检测的字符串) 例如 reg.test(str) 返回true/false
检索符合规则的字符串
regObj.exec(被检测的字符串) 例如 reg.exec(str) 返回一个数组/null
元字符
- 边界符 用什么开头,用什么结尾
- 量词 重复次数
- 字符类 表示0~9
边界符
- ^ 表示匹配行首的文本,即以谁开始
- $ 表示匹配行尾的文本,即以谁结束
- ^ $ 同时出现,表示精确匹配
量词
- * 重复0次或多次
- + 重复1次或多次
- ?重复0次或1次
- {n} 重复n次
- {n,} 重复n次或更多次
- {n,m} 重复n~m次
逗号左右不能有空格!
字符类
- [] 匹配字符集合,即只要包含括号中任一个字符,返回true。
- [a-zA-Z] 加连字符表示一个范围。
- [^a-z] 表示除了小写字母以外的字符。
- . 匹配除换行符之外的任一个字符。
修饰符
修饰符约束某些细节行为。
- /表达式/i 表示不区分大小写。
- /表达式/g 表示匹配所有正则表达式的结果。(过滤敏感字)
- 字符串.replace(/正则表达式/, '替换的文本') 替换。
JS 高级
垃圾回收机制
垃圾回收机制,简称GC。
JS中内存的分配和回收是自动完成的,内存不使用时会被垃圾回收器自动回收。
内存生存周期
- 内存分配:声明对象、函数、变量的时候,系统会自动为其分配内存。
- 内存使用:读写变量、使用函数。
- 内存回收:自动回收不再使用的内存。
全局变量一般不会回收。
堆栈空间分配区别:
- 栈:由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
- 堆:一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放堆里面。
常见浏览器垃圾回收算法
- 引用技术算法(IE):定义内存不再使用,就是看一个对象是否有指向它的引用,没有引用了就回收对象。但这个方式存在问题:嵌套引用:如果两个对象互相引用,尽管它们不再使用,垃圾回收器不会进行回收,导致内存泄漏。
- 标记清除算法(现代浏览器大多基于此算法的改进算法):从根部出发定时扫描内存中的对象,凡是能够到达的对象,都是还需要使用的;无法到达的对象标记为不再使用,稍后回收。
闭包
内层函数访问到外层函数的作用域。但是外层函数不能访问内部函数的作用域。
作用是封闭数据,提供操作,试外部可以访问内部变量。
function outer() {
let i = 1;
function fn(){
console.log(i);
}
return fn;
}
const fun = outer();
fun();
// 1
// 原本在函数outer外不能访问i
// 由于闭包的特性
// 可以在外部访问i
实现数据的私有
// 常规写法
// 可以直接修改count
let count = 0;
function fn() {
count++;
console.log(`函数被调用${count}次`);
}
fn(); // 1
fn(); // 2
// 闭包实现数据私有
// 外部无法修改count
function fn() {
let count = 0;
function fun(){
count++;
console.log(`函数被调用${count}次`);
}
return fun();
}
const result = fn();
result(); // 1
result(); // 2
闭包可能引起内存泄漏。
函数高级
函数参数
动态参数
- arguments 是函数内置的伪数组变量,包含了调用函数时传入的所有实参。
- arguments 只存在于函数中。
可以通过for循环依次访问所有实参。
剩余参数
将不定数量的参数表示一个数组。
- ... 是语法符号,置于参数列表最后端,用于获取多余的实参。
- ... 获取的实参,是一个真数组。
更提倡
展开运算符(...),将一个数组展开
// 求最值
const arr = [1, 5, 2, 8, 3];
console.log(Math.max(...arr));
console.log(Math.max(1, 5, 2, 8, 3));
console.log(Math.min(...arr));
// 合并数组
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [...arr1, ...arr2];
console.log(arr3);
箭头函数
更简短的函数写法且不绑定this,语法比函数表达式更简洁。更适用于本需要匿名函数的地方。
基本语法
const fn = function () {
console.log(1);
}
fn();
const fn = () => {
console.log(1);
}
fn();
只有一个参数省略小括号
const fn = x => x + x;
只有一行代码可以写到一行,并并无需return
const fn = (x, y) => x + y;
加括号的函数体返回对象字面量表达式
const fn1 = name => ({uname: name})
console.log(fn1('lzk'))
箭头函数没有arguments动态参数,但是有...args剩余参数。
箭头函数不会创建自己的this,它从作用域链的上一层沿用this。
btn.addEventListener("click", function(){
console.log(this)
})
// output: btn
btn.addEventListener("click", () => {
console.log(this)
})
// output: window
在开发中需要考虑函数中this的值,事件回调函数时为了简便不推荐使用箭头函数。
解构赋值
一种快速为变量赋值的简洁语法。
数组解构
const arr = [1, 2, 3];
const [a, b, c] = arr;
const [a, b, c] = [4, 5, 6];
[b, a] = [a, b];
变量数量大于单元值数量时,多余的变量将被赋值为undefined
单元值数量多于单元值数量时,可以用剩余参数存储多余变量。
为防止多余变量被赋值为undefined,可以设置默认值。
可以忽略某些值。
const [a = 1, , b = 2, c = 3] = [4, 5, 6, 7];
对象结构
将对象属性和方法快速赋值给一系列变量。
const user = {
name: 'John',
age: 36
}
const { name, age } = user
console.log(name)
console.log(age)
对象的值将被赋给属性相同的变量。找不到属性相同的变量被赋值为undefined
变量重新命名
const { name: uname, age } = user
console.log(uname)
console.log(age)
单极&多级对象解构
const people = {
name: 'John',
family: {
mother: 'John',
father: 'George',
sister: 'John'
},
age: 18
}
const { name, family: { mother, father, sister } } = people
const people = [{
name: 'John',
family: {
mother: 'John',
father: 'George',
sister: 'John'
},
age: 18
}]
const [{ name, family: { mother, father, sister } }] = people
forEach()
const arr = ['pink', 'red', 'orange']
arr.forEach(function(item, index) {
console.log(`当前数组元素${item}`)
console.log(`当前数组索引${item}`)
})
filter()
const score = [10, 50, 3, 40, 33]
const re = score.filter(function (item) { return item > 30 })
console.log(re)
对象
构造函数
- 通过对象字面量创建对象
- 用new Object创建对象
- 利用构造函数创建对象
构造函数在技术上是常规函数,命名以大写字母开头,用new操作符执行。
function Pig(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
const peppa = new Pig('peppa', 18, '女');
const george = new Pig('george', 3, '男');
通过构造函数创建的对象或函数是实例对象/实例函数
构造函数的属性/方法被称为静态成员。一般公共的属性或方法被成为静态成员,静态成员中的this指向构造函数本身。
function Pig(name, age, gender) {
}
Pig.eyes = 2
Pig.arms = 2
Pig.walk = function () {
console.log(this.eyes);
}
内置构造函数
const user = new Object({name: 'John', email: '123@example.com'});
// 推荐使用字面量声明而不是Object构造函数
const o = {name:'John', email: '123@example.com'};
获取对象中所有属性
const o = {name:'John', email: '123@example.com'};
const arr = Object.keys(o);
console.log(arr);
获取对象中所有属性值
const o = {name:'John', email: '123@example.com'};
const arr = Object.values(o);
console.log(arr);
对象拷贝
const o = { name: 'John', email: '123@example.com' };
const obj = {};
Object.assign(obj, o);
console.log(obj);
原型
前面传统构造函数很方便,但是存在内存浪费的问题。通过原型对象能实现方法共享。构造函数通过原型分配的函数是所有对象所共享的。
JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象,我们称之原型对象。这个对象不会挂载函数,对象实例化不会多次创建原型上挂载的函数,节约内存。
可以将不变的函数都挂载在prototype上,节约内存。
构造函数和原型对象中的this都指向实例化的对象。
constructor属性
每个原型对象里都有个constructor属性,该属性指向该原型对象的构造函数。
如果用对象赋值的形式给prototype赋值多个函数,会覆盖原型对象原来内容,则要手动加上constructor指向原来的构造函数。
function Star(name) {
this.name = name;
}
Star.prototype = {
constructor: Star,
sing: function () { console.log('sing') },
dance: function () { console.log('dance') }
}
console.log(Star.prototype.constructor)
对象原型
对象都会有一个属性__proto__指向构造函数的prototype原型对象。
- __proto__是JS非标准属性
- [[prototype]]和__proto__意义相同
- 用来表明当前实例对象指向哪个原型对象prototype
- __proto__对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数。
原型继承
JS中大多是借助原型对象实现继承的特性。
const People = {
head:1,
eyes:2,
legs:2,
say:function(){},
eat:function(){}
}
function Man(){}
Man.prototype = People
Man.prototype.constructor = Man
const pink = new Man()
console.log(pink)
吧共性抽出来单独创建类,然后赋值给原型对象,最后指回constructor。
问题是如果多个子类继承同一个父类,更改其中一个子类的原型,另一个子类也被影响,因为继承自同一原型。解决方法:不适用同一个对象。
const People = {
head:1,
eyes:2,
legs:2,
say:function(){},
eat:function(){}
}
function Man(){}
Man.prototype = new People()
Man.prototype.constructor = Man
const pink = new Man()
console.log(pink)
原型链
基于原型对象的继承使得不同构造函数的原型对象以链状关联在一起,这种链状结构称为原型链。
访问一个对象的属性或方法时先查找对象自身,没有就向原型查找,知道找到或查到null为止。
可以用instanceof运算符检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
深浅拷贝
浅拷贝的问题
const pink = {
name: 'pink',
age: 36
}
const red = pink
red.name = 'red'
console.log(red.name) // red
console.log(pink.name) // red
浅拷贝拷贝的是地址。
深拷贝
通过递归实现深拷贝
const o = {}
function deepCopy(newObj, oldObj) {
for (let k in oldObj) {
if (oldObj[k] instanceof Array) {
newObj[k] = []
deepCopy(newObj[k], oldObj[k])
}
else if (oldObj[k] instanceof Object) {
newObj[k] = {}
deepCopy(newObj[k], oldObj[k])
}
else {
newObj[k] = oldObj[k]
}
}
}
// 简版
function deepCopy(newObj, oldObj) {
for (let k in oldObj) {
newObj[k] = oldObj[k]
}
}
JavaScript库lodash里cloneDeep内部实现了深拷贝
const obj = {
uname: 'uname',
age: 18,
hobby: ['basketball', 'football'],
family: { baby: 'baby' }
}
const o = _.cloneDeep(obj)
通过JSON.stringify()实现
const obj = {
uname: 'uname',
age: 18,
hobby: ['basketball', 'football'],
family: { baby: 'baby' }
}
const o = JSON.parse(JSON.stringify(obj))
异常处理
预估代码可能产生的错误,最大程度避免发生的错误导致整个程序无法继续运行。
throw抛出异常,结合Error对象设置详细的错误信息。
throw new Error('Invalid')
try/catch捕获浏览器提供的错误信息。将可能产生错误的代码放在try中,try代码块出错后会执行catch代码块,并截获错误信息。不管是否有错误,finally代码块都会执行。
function foo() {
try {
const p = document.querySelector('.p')
p.style.color = 'red'
} catch (error) {
console.log(error.message)
// 种植代码继续执行
return
}
finally {
alert('foo')
}
console.log('如果出错,我不会执行')
}
处理this
fun.call(thisarg, arg1, arg2, ...)
const obj = {
name: 'foo'
}
function test(x, y) {
console.log(this)
console.log(x + y)
}
test.call(obj, 1, 2)
fun.apply(thisarg, [argsArray]) 主要处理数组
fun.bind(thisarg, arg1, arg2, ...) 不会调用函数,但是改变this指向。返回原函数拷贝成的新函数。
function sayHi(){
console.log(this)
}
let user = {
name: 'John',
age: 13
}
let sayHello = sayHi.bind(user)
sayHello()
性能优化
节流:连续触发事件,但是在n秒中只执行一次函数。
使用场景:轮播图点击、鼠标移动、尺寸缩放、滚动条滚动。
例如,鼠标在盒子上移动,里面数字+1.如果不节流,每次鼠标移动就会有大量操作。
核心思路:移动后时间 - 开始移动的时间 >= 500ms 才执行mouseMove函数。
防抖:触发事件后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则重新计算函数执行时间。
使用场景:假设输入就可以发送请求,但是输入快的话就会发送很多请求。设定一个时间300ms,输入第一个字符时300ms后发送请求,但是200ms的时候又输入了一个字符,则再等300ms后发送。即在200ms鼠标移动时判断是否有定时器,有就清除,再开启新的计时器。
异步编程
常见使用场景:用于处理一些耗时操作,比如I/O、网络请求、耗时计算等等。早期处理异步通常使用事件和回调来处理。如间歇函数或延时函数。代码类似于:
asyncFunc(args, function(result){
// 处理异步结果
})
如果后续代码依赖异步请求的结果,只能将逻辑放在回调函数中,如果有多层依赖,就会出现回调嵌套、回调地狱。
Promise
Promise是一个函数返回的对象,可以在上面绑定回调函数,它能把异步操作最终的成功返回值或者失败原因和相应处理程序绑定起来。
Promise的状态:
- 待定:初始状态
- 已兑现:操作成功完成
- 已拒绝:意味操作失败
只有异步操作的结果可以改变promise的状态。promise状态的改变只有待定到已兑现,或待定到已拒绝,一旦状态改变就定型,不能再变,且任何时候都可以得到其结果。
promise的缺点:
- 无法取消promise
- 如果不设置回调函数,promise内部错误不会反映到外部
- 处于待定状态时不能判定进展
构造Promise实例:(resolve和reject由JS引擎提供)
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
resolve函数:将Promise对象状态变为已兑现,在异步操作成功时调用,并将异步操作的结果作为参数传递出去。
reject函数:将Promise对象的状态变为已拒绝,在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。
Promise实例生成后可以用then方法指定resolve状态和rejected状态的回调函数。
promise.then(function (value) {/* success */ }, function (error) {/* failure */ });
then方法可接受两个函数作为参数,均为可选项。都接受Promise对象传出的值作为参数。
Promise.all() 方法用于将多个实例批量执行,所有请求结束后同一返回。
const p = Promise.all([p1, p2, p3]).then(function (posts){})
p1 p2 p3是多个异步请求实例。
- 只有p1 p2 p3的状态都为fulfilled,p的状态才变为fulfilled,此时三个函数返回值以数组形式传递给p的回调函数。
- 只要三个函数中有一个被rejected,p状态就变为rejected,被第一个rejected的实例的返回值会传递给p的回调函数。
async
async函数使得异步操作变得更加方便。
async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await会先返回,等异步操作完成,再执行后面的语句。
async function getStockPriceByName(name) {
const symbol = await getSymbolByName(name);
const price = await getStockPrice(symbol);
return price;
}
getStockPriceByName('goog').then(function (result) {
console.log(result);
})
这是一个获取股票票价的例子,async关键字代表函数内部有异步操作。调用该函数时会立即返回一个Promise对象。
箭头函数
const foo = async () => {}
async函数返回一个Promise对象,函数内部return语句返回的值会成为then方法回调函数的参数。
async function fn() {
return 'hello';
}
fn().then(v => console.log(v));
async函数内部抛出错误,会导致Promise对象变为reject状态。抛出错误的对象会被catch方法回调函数接收。
async function f() {
throw new Error('Failed')
}
f().catch(e => console.log(e))
async函数返回的Promise对象,必须等到内部所有await执行完,才会发生状态改变,除非遇到return或者抛出错误。也就是只有async内部操作执行完才会执行then方法指定的回调函数。
另外async函数执行时内部是顺序执行的,await操作会阻塞后面的操作。
利用await的阻塞效果,可以实现休眠效果。
function sleep(interval) {
return new Promise((resolve) => {
setTimeout(resolve, interval)
})
}
async function fn(){
console.log('running')
await sleep(1000)
console.log('done')
}
fn()
如果async函数无return且没错误,则then函数中的参数为undefined
async function f() {
await Promise.resolve('success')
// await Promise.reject('error')
}
f().then(v => console.log(v)).catch(err => console.log(err));
// undefined
await出错的情况下无return也会给catch返回错误参数
async function f() {
// await Promise.resolve('success')
await Promise.reject('error')
}
f().then(v => console.log(v)).catch(err => console.log(err));
// error
任何一个await语句后面的Promise对象变为reject状态,整个async函数都会中断执行。
如果不希望后面的await被打断,可以把可能出错的await语句放在try/catch中。另一种方法是await后面的Promise对象再跟一个catch方法。
async function f() {
await Promise.reject('error')
.catch(err => console.log(err));
}
async不是一开始就异步执行,只有在遇到await后才开始异步。
多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
let [foo, bar] = await Promise.all([getFoo(), getBar()])
Ajax
传统的网页需要更新内容必须重载整个页面,而Ajax在后台与服务器进行少量数据交换,可以实现网页某部分异步更新。
XMLHttpRequest
XMLHttpRequest - Web API 接口参考 | MDN
XHR可以在不刷新页面的情况下请求特定URL,获取数据。这允许网页在不影响用户操作的情况下更新页面的局部内容。
Ajax工作原理:
- 客户端发送请求给xhr
- xhr把请求提交给服务器
- 服务器进行业务处理,把响应数据交给xhr
- xhr得到数据,由JavaScript写到网页上。
Ajax的创建步骤:
- 创建xhr对象,及一个异步调用对象
- 创建一个新的http请求,并指定请求的方法、url及验证信息
- 设置相应http请求状态变化的函数
- 发送http请求
- 获取异步调用返回的数据
- 使用JavaScript实现局部刷新
// 创建XHL对象
const request = new XMLHttpRequest();
// 创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息
// method 为 HTTP方法,如GET POST PUT 等
// url 为目标url地址
// async(可选) 一个布尔值参数,表示是否异步执行,默认true
// user(可选) 用于认证的用户名,默认NULL
// password(可选) 用于认证的密码,默认NULL
request.open(method, url, async, user, password);
request.send();
从创建xhl对象开始,其会经历以下5种状态:
- 未初始化状态,此时XHR对象readyState值为0
- 初始化状态,使用了open()函数后,readyState值为1
- 发送数据状态,初始化以后使用send()发送数据时,readyState值为2
- 接收数据状态,readyState值为3
- 完成状态,接收数据完毕后,readyState值为4
只读属性XMLHttpRequest.status返回了XHR相应中的数字状态码。在请求完成前,status的值为0,如果 XMLHttpRequest出错,浏览器返回的status也为0.
- UNSENT 0
- OPENED 0
- LOADING 200
- DONE 200
只有在XHR对象完成以上5个步骤后,才可以获取服务端的数据。因此拿数据前要先判断XHR状态
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
// TODO
}
}
案例:
let httpRequest;
document.getElementById('ajax').addEventListener('click', makeRequest);
function makeRequest() {
httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = alertContents;
httpRequest.open('GET', 'test.html', true);
httpRequest.send();
}
function alertContents() {
if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status === 200) {
alert(httpRequest.responseText);
}
else {
alert('Error: ' + httpRequest.status)
}
}
}
fetch
全局的fetch方法用于发起获取资源请求。它返回一个promise,这个promise会在请求响应后被resolve,并传回Response对象。
参数:
- input 一个USVString字符串,包含要获取资源的URL/一个Request对象。
- init 配置项选项,包括所有对请求的设置。可选参数有:method、headers、body等等。
例子
const myImage = document.querySelector('img');
const myHeader = new Headers().append('Content-Type', 'image/jpeg');
const myInit = {
method: 'GET',
header: myHeader,
mode: 'cors',
cache: 'default'
}
// 新建一个Request对象
const request = new Request('flowers.jpg');
// 获取资源
fetch(request, myInit)
.then(function (response) {
// 为了图片解析正常,设置相应MIME类型
return response.blob();
})
.then(function (response) {
const objectUrl = URL.createObjectURL(response);
myImage.src = objectUrl;
})
代理工具
- 安装 npm install -g whistle
- 使用 w2 start
- 打开http://127.0.0.1 并安装证书
- 搭配Proxy SwitchyOmega使用更方便