一、作用域
1、概念
了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
作用域规定了变量能够访问的“范围”,离开了这个“范围”变量便不能被访问。
作用域分为局部作用域和全局作用域。
2、作用域链
作用域链本质上是底层的变量查找机制。
- 在函数被执行时,会优先在当前函数作用域中查找变量
- 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
3、垃圾回收机制(GC)
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收机制自动回收。
引用计数法:
- 方法:追踪被引用的次数,如果被引用一次记录次数就+1,减少一个引用就-1,如果引用次数是0则释放内存。
- 问题:嵌套引用(循环引用),如果两个对象相互引用,垃圾回收器不会进行回收,导致内存泄漏。
标记计数法:现代的浏览器不再使用引用计数法,而是使用这个。
- 方法:标记清除法将“不再使用的对象”定义为“无法到达的对象”。就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是需要使用的。那些无法从根部出发触及的对象被标记为不再使用,稍后进行回收
4、闭包
- 概念:一个函数对周围函数状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域。 闭包:内层函数+外层函数的变量。
- 作用:封闭数据,提供操作,外部也可以访问函数内部的操作
function outer() {
let a = 10
function fn(){
console.log(a)
}
return fn()
}
const fun = outer()
fun() //调用fun,即外部也可以访问函数内部的操作。
outer必须赋值给fun(),才能保持根引用,防止回收机制回收变量。这样outer在调用的时候就不会被回收了
4、变量提升
变量提升是javascript中比较奇怪的现象,它允许在变量声明之前被访问(仅存在于var声明变量)。
- 做法:把所有var声明的变量提升到当前作用域的最前面。但只提升声明,不提升赋值
console.log(num)
var num = 10
//根据变量提升原则,在解析式变成
var num
console.log(num) //这时候变量的值是undefined
num = 10
二、函数提升
1、函数提升
函数提升和变量提升比较类似,是指函数在声明前即可被调用。
fn()
function fn(){
console.log('函数提升')
}
//根据函数提升原则,会把所有函数声明提升到当前作用域的最前面
function fn(){
console.log('函数提升')
}
fn()
2、函数参数
1)动态参数
arguements是函数内部的伪数组变量,它包含了调用函数时传入的所有参数
function function(){
for(let i = 0;i < arguement.length;i++){
}
}
2)剩余参数
剩余参数允许我们将一个不定数量的参数表示为一个数组。
…是语法符号,置于最末函数形参之前,用于获取多余的参数。
function function(a,b,...arr){
}
3)区分
推荐开发中使用剩余参数(箭头函数中没有arguements)
动态参数是虚数组
剩余参数是真数组
4)展开运算符
- 展开数组
const arr = [1,5,3,8,3]
console.log(...arrr) //1 5 3 8 2
//运用
console.log(Math.max(...arr)) //8
const joinarr=[...arr1,...arr2]
3、箭头函数
- 引入箭头函数的目的是更简短的函数写法且不绑定this,箭头函数的语法比函数表达式更简洁。
- 箭头函数更适用于那些本来需要匿名函数的地方
- 基本语法
箭头函数属于表达式函数,因此不存在函数提升。
const fn = function(){
}
//箭头函数
const fn = () => {
}
//只有一个形参,括号可以省略
const fn = x => {
}
//只有一行代码,可以省略大括号
const fn = x => console.log(x)
//只有一行代码,可以省略return
const fn = x => x+x
//可以直接返回一个对象
const fn = (uname) => ({name:uname})
- 箭头参数
箭头函数没有arguements动态参数,但是有剩余参数…args - this
- 普通函数根据它是如何被调用来定义这个函数的this值(谁调用this,this指向谁)
- 普通函数没有明确调用者时this值为window,严格模式下没有调用者时this的值为undefined
'use strict' //开启严格模式
- 箭头函数不会创建自己的this,它只会从自己作用链的上一层去沿用this,一层一层查找this直到this有定义(上一级this指向谁它就指向谁)
三、解构赋值
1、数组解构
- 作用:数组结构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
const [max,min,avg] = [100,60,80]
//交换变量
const a=1
const b=2; // 这里必须加分号
[b,a] = [a,b]
//利用剩余变量
[a,b,..arr]=[1,2,3,4,5]
//变量多没赋值会设置成undefined,可以设一个默认值
[a=0,b=0]=[]
//按需导入,忽略某些值
[a, ,b,c]=[1,2,3,4]
//多层结构
[a,b,[c,d]] = [1,2,[3,4]]
PS:js前面必须加分号的两种情况
- 立即执行函数
(function t() {})();
//或者
;(function t() {})()
- 数组解构
//数组开头的,特别是前面有语句的一定注意加分号
;[b,a] = [a,b]
2、对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法。
const obj = {
name:'张三',
age:18
}
//必须保证属性名和变量名一致
const {name,age} = {name:'张三',age:18}
//对象解构的变量名,可以重新改名
const {name:username,age} = {name:'张三',age:18} //将name改成username
//部分截取
const {name} = {obj}
//解构数组对象
const pig = [{
uname:'佩奇'
age:18
}]
const [{uname,age}] = pig
3、拓展:forEach()
forEach()方法主要用于调用数组每个元素,并将元素传递给回调函数
被遍历数组.forEach(function(当前数组元素[,当前元素索引号])){
//函数体
}
四、构造函数
1、深入对象
1)创建对象三种方式
1.利用字面量创建
const o = {
name:''
}
2. 利用new Object创建对象
const o = new Object({
name:''
})
3.利用构造函数创建对象
2)构造函数
- 语法
一个特殊函数,主要用来初始化对象
function People(name,age,gender){
this.name= name
this.age=age
this.gener=gender
}
//创建实例对象
const p = new People('s',6,女)
- 约束
- 只能以大写字母开头
- 只能由new操作符执行
3)实例成员/静态成员
- 通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员(实例属性和实例方法)
- 构造函数的属性和方法称为静态成员(静态属性和静态方法)
2、内置构造函数
字符串,数值,布尔等基本类型都有专门的构造函数,称为包装类型
1)Object
三个静态方法
- Object.keys(对象)
获取所有对象中的属性(键)
const o = {name:'佩奇',age:6}
const arr = Object.keys(o) //返回一个数组
- Object.values(对象)
获取所有对象中的属性值 - Objject.assign
用于对象拷贝
const o = {name:'佩奇',age:6}
const obj={}
Objject.assign(obj,o)
本质上是追加合并
const o = {name:'佩奇',age:6}
Objject.assign(o,{gender:女}) //const o = {name:'佩奇',age:6,gender:'女'}
2)Array
常用方法
- reduce
arr.reduce(function(上一次值,当前值){},起始值)
执行过程
- 如果没有初始值,则上一次值以数组的第一个数组元素的值
- 每一次循环,把返回值给做为下一次循环的上一次值
- 如果有起始值,则起始值作为上一次的值
- join
数组元素拼接为字符串,返回字符串 - find
查找元素,返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined - every
检测数组所有元素是否都符合指定条件,如果所有元素都通过检测返回 true,否则返回 false - some
检测数组中的元素是否满足指定条件 如果数组中有元素满足条件返回 true,否则返回 false - concat
合并两个数组,返回生成新数组 - sort
对原数组单元值排序 - splice
删除或替换原数组单元 - reverse
反转数组 - findIndex
查找元素的索引值
3)String
常用方法
- length
用来获取字符串的度长 - split(‘分隔符’)
用来将字符串拆分成数组 - substring
用于字符串截取 - startWith
检测是否以某字符开头 - includes
判断一个字符串是否包含在另一个字符串中,根据情况返回true 或 false - toUpperCase
用于将字母转换成大写 - toLowCase
用于将就转换成小写 - indexof
检测是否包含某字符 - endsWith
检测是否以某字符结尾 - replace
用于替换字符串,支持正则匹配 - match
用于查找字符串,支持正则匹配
4)Number
- toFixed()
设置保留小数位的长度
五、深入面向对象
构造函数体现了面向对象的封装特性
封装可能的问题:当一个函数使用与实例对象无关时,需要变成静态的防止每次创建都生成一模一样造成内存浪费。即有些函数时所有对象公用的,这样节省内存。
1、原型
- 目标:使用原型对象实现方法共享。构造函数通过原型分配的函数是所有对象所共享的。
- 原理
js规定,每一个构造函数都有一个prototype属性( o b j . p r o t o t y p e obj.prototype obj.prototype),指向另一个对象,所以我们也称为原型对象,这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存。
因此可以把那些不变的方法直接定义在prototype对象上,这样所有对象的实例都可以共享这些方法
构造函数和原型对象中的this都指向实例化对象
Obj.prototype.sing = funciton(){
//
}
//调用
obj.sing()
2、constructor属性
- 每个原型对象(prototype)里面都有个constructor属性(constructor构造函数)
该属性指向该原型对象的构造函数
Obj.prototype.constructor = Obj
- 如果有多个对象的方法,可以给原型对象采取对象形式赋值,但是这样会覆盖原型对象原来的内容,这样修改后原型对象constructor就不再指向当前构造函数,因此需要在修改后的原型对象上添加一个constructor指向原来的构造函数。
Obj.prototype={
constructor:Obj,
fn1:function(){},
fn2:function(){}
}
3、对象原型
- 对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在。
- __proto__是JS非标准属性。[[prototype]]和__proto__意义相同,只读不能拿来赋值。用来表明当前实例对象指向哪一个原型对象prototype。
obj,__proto__=Obj.prototype
- __proto__里面也有一个constructor属性,指向创建该实例对象的构造函数
obj,__proto__.constructor=Ob
4、原型继承
继承是面向对象编程的另一个特征,JS大多是借助原型对象实现继承的特性。
const Person = {
eyes:2,
head:1
}
function Woman(){
}
//公共的部分可以放在原型上,因此Woman通过原型继承Person
Woman.prototype=new Person()
//覆盖后需要重新指回原constructor
Woman.prototype.constructor=Woman
//注意不能写成Woman.prototype=Person
不同对象使用同一继承对象会造成错误
5、原型链
原型对象也是一个对象,也有自己的对象原型,是最高级的Object
Obj..prototype.__proto=Object.prototype
- 通过__proto__连起来的就是原型链。
- 基于原型对象的继承使不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的关系,我们将原型对象的链状解构称为原型链
- 原型链本质上是对应一个查找规则
- 当访问一个对象的属性(包括方法)时,首先查找:这个对象自身有没有该属性。
- 如果没有就查找它的原型(也就是_proto_指向的 prototype 原型对象)
- 如果还没有就查找原型对象的原型(Object的原型对象)
- 依此类推一直找到 Object 为止(null)
- __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
- 可以使用 instanceof 运算符用于检测构造函数的porototype 属性是否出现在某个实例对象的原型链上
六、深浅拷贝
1、浅拷贝
- 拷贝对象之后,里面的属性值是简单数据类型的直接拷贝值。
- 如果属性值是引用类型则拷贝的是地址。
1. 直接赋值obj1=obj2
2. 浅拷贝:Object.assign(obj1,obj2)
3. 浅拷贝:obj1={...obj2}
- 直接赋值的方法,只要是对象,都会互相影响,因为是直接拷贝对象栈里面的地址。
- 浅拷贝如果是一层对象,不互相影响(值拷贝过来了),如果出现多层对象还会互相影响。
2、深拷贝
1)方法
- 通过函数递归实现深拷贝
- lodash/cloneDeep
- 通过JSON.stringify()实现
1. 递归
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]
}
}
}
2. lodash/cloneDeep
//js库loadash里面cloneDeep内部实现了深拷贝
newObj = _.cloneDeep(oldObj)
3. 通过JSON.stringify()实现
//这个函数把对象转换成字符串,字符串没有任何赋值或者引用的问题
oldObj = JSON.parse(JSON.stringify(newObj))
七、异常处理
1、throw抛异常
throw new Error('错误信息')
- throw抛出异常信息,程序也会终止执行
- throw后面跟的是错误提示信息
- Error对象配合throw使用,能够设置更详细的错误信息
2、try/catch捕获异常
通过try/catch捕获错误信息(浏览器提供的错误信息)
try{
}catch(error){
//error.message展示的是错误信息
//拦截错误,提示浏览器提供的错误信息,但是不中断程序的执行
//利用return中断
return
}finally{
//不管程序错误与否都执行
}
3、debugger
关键字,程序执行在这里会产生断点
八、改变this
js允许指定函数中this的指向改变,有三个方法可以动态指定普通函数中this的指向
1、call()
使用call方法调用函数,同时指定被调用函数中this的值
fn.call(thisArg,arg1,arg2,...)
//thisArg:在 fun 函数运行时指定的 this 值
//arg1,arg2:传递的其他参数,即fn参数有什么传什么。
//返回值就是函数的返回值,因为它就是调用函数
2、apply()
使用apply方法调用函数,同时指定被调用函数中this的值
fn.apply(thisArg,[argsArray])
//thisArg:在 fun 函数运行时指定的 this 值
//argsArray:传递的值,必须包含在数组里面
//返回值就是函数的返回值,因为它就是调用函数
3、bind()
bind不会调用函数,但是能改变函数内部this指向
因此当只是想改变this指向,并且不想调用这个函数的时候,可以使用bind,比如改变定时器内部的this指向
const f = fn.bind(thisArg,arg1,arg2,...)
//thisArg:在 fun 函数运行时指定的 this 值
//arg1,arg2:传递的其他参数,即fn参数有什么传什么。
//返回由指定的this值和初始化参数改造的原函数拷贝(新函数)
//调用
f()
九、性能优化
1、防抖
- 概念:单位时间内,频繁触发事件,只执行最后一次。
实现方式:
- lodash提供的防抖来处理
_.debounce(func,[wait=0],[options=])
创建一个 debounced(防抖动)函数,该函数会从上一次被调用后,延迟 wait 毫秒后调用 func方法。debounced(防抖动)函数提供一个 cancel方法取消延迟的函数调用以及 flush方法立即调用。(源码内部本质上就是一个定时器)
*了解:可以提供一个options(选项)对象决定如何调用 func 方法,options.leading 与|或 options.trailing 决定延迟前后如何触发(注:是 先调用后等待 还是 先等待后调用)。func调用时会传入最后一次提供给 debounced(防抖动)函数 的参数。后续调用的 debounced(防抖动)函数返回是最后一次 func 调用的结果。
- 底层实现
function debounce(fn,t){
let timer
return function(){
if(timer) clearTimeout(timer)
timer = setTimeout(function(){
fn()
},t)
}
}
//闭包写法,通过return将局部变量变成全局变量,timer能一直保留从而可以判断
2、节流
- 单位时间内频繁触发事件,只执行一次
实现方式:
- lodash提供的节流来处理
_.throttle(func,[wait=0],[options=])
创建一个节流函数,在 wait 秒内最多执行 func 一次的函数。该函数提供一个 cancel方法取消延迟的函数调用以及 flush方法立即调用。
*了解:可以提供一个 options 对象决定如何调用 func 方法,options.eading 与|或 options.trailing 决定 wait 前后如何触发。 func 会传入最后一次传入的参数给这个函数。 随后调用的函数返回是最后一次 func 调用的结果。
*了解:注意:如果 leading 和 trailing 都设定为 true 则 func 允许 trailing 方式调用的条件为: 在 wait 期间多次调用。如果 wait 为@并且 leading 为 false,func调用将被推迟到下一个点,类似setTimeout 为@的超时。
2. 底层实现
function debounce(fn,t){
let timer
return function(){
if(!timer) { //没有定时器
timer = setTimeout(function(){
fn()
//清空定时器
timer=null
//注意在setTimerout中是无法删除定时器的,因为定时器还在运作,所以不能使用clearTimeout(timer)
},t)
}
}
}
//闭包写法,通过return将局部变量变成全局变量,timer能一直保留从而可以判断