1 事件循环机制
因为js 是单线程的,如果有两个线程对DOM的操作有冲突的,为了解决阻塞的情况,所以有一个event loop 机制。
当javascript代码执行的时候会将不同的变量存于内存中的不同位置:堆(heap)和栈(stack)。其中,堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针。
当我们调用一个方法的时候,js会生成一个与这个方法对应的执行环境(context),又叫执行上下文。 这个执行环境中存在着这个方法的私有作用域,上层作用域的指向,方法的参数,变量以及这个作用域的this对象。
而当一系列方法被依次调用的时候,因为js是单线程的,同一时间只能执行一个方法,于是一系列的方法被排队在执行栈中。
当一个脚本第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。
如果当前执行的是一个方法,那么js会向执行栈中添加这个方法的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。
这个过程反复进行,直到执行栈中的代码全部执行完毕。
一个方法执行会向执行栈中加入这个方法的执行环境,在这个执行环境中还可以调用其他方法,甚至是自己,其结果不过是在执行栈中再添加一个执行环境。这个过程可以是无限进行下去的,除非发生了栈溢出。以上的过程说的都是同步代码的执行。
js引擎遇到一个异步事件后,js会将这个事件加入事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈执行完毕,它会立刻先处理微任务队列中的事件,然后再去宏任务队列中取出一个事件。
主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。
2.macro task与micro task
不同的异步任务被分为两类:先: 宏任务(macro task)**:整体代码,,setTimeout(), setInterval() ** 和 微任务(micro task):new Promise().then里面的回调
为什么要区分宏任务和微任务: 由于所有的任务保存在事件队列中,是先进先出的,但有些任务的优先级很高,所以引入了微任务的概念,为了保证任务完成的顺序。 js会先完成宏任务后,在完成微任务队列中的任务。
-
Node中的事件循环 宏任务执行顺序:
1 timer 定时器: 执行已经安排的setTimeout 和 setInterval 的回调函数 2 pending callback 待定回调:执行延迟到下一个循环迭代的I/O 回调 3 idle,prepare:仅系统内部使用 4 Poll: 检索新的I/O事件, 执行I/O 回调 5 check:执行setImmediate()回调函数 6 close callbacks :socket.on('close',()=>{})
-
微任务 和 宏任务 在 node 中的执行顺序
Node v10 及以前:
1 执行完一个阶段中的所有任务
2 执行nextTick队列里的任务
3 执行完微任务队列的内容
Node V10以后的:和浏览器的行为统一了
//1 函数async1定义,但未调用,所以现在不输出
async function async1() {
// 6
console.log('async1 start')
//7
await async2()//比较重要的点,await 中执行的函数async2(),可以理解为将async2()放入 new Promise(() =>{})里,执行async2,打印async2
//8 后面的的执行语句都相对于放在了 .then()中,属于微任务,加入了微任务队列中,先不执行,执行当前的宏任务)
//13 第一个微任务,打印
console.log('async1 end')
}
//2 函数async2定义,但未调用,所以现在不输出
async function async2() {
console.log('async2')
}
//3 打印
console.log('script start')
// 4 setTimeout是宏任务,优先级低于微任务,会被移到下一次的宏任务队列中去,现在不输出
setTimeout(function () {
//15 打印
console.log('setTimeout')
},0)
// 5 这里执行async1,所以输出 'async1 start'
async1()
//9
new Promise(function (resolve) {
// 10 同步代码,打印
console.log('Promise1')
//11 .then是异步的,是微任务,先不执行
resolve()
}).then(function () {
//14 第二个微任务,打印,此时,微任务队列清空了,接下来开启下一轮的宏任务,也就是setTimeout
console.log('Promise2')
})
//12 打印,此时第一遍的宏任务已经执行完成,接下来清空微任务队列
console.log('script end')
/*打印顺序:
script start
async1 start
async2
Promise1
script end
async1 end
Promise2
setTimeout
*/
//1 打印
console.log('start')
// 2 宏任务,先不执行,放到下一轮
//8 执行第二行宏任务
setTimeout(()=>{
// 9 打印
console.log('children2')
//10 这里相对于直接将.then中的回调加入了微任务队列中,
Promise.resolve().then(()=>{
//11 打印
console.log('children3')})
},0)
// 3
new Promise(function (resolve, reject) {
// 4 同步的,打印
console.log('children4')
// 5 宏任务,先不执行,放到下一轮
// 12 执行第三轮宏任务
setTimeout(function () {
//13 打印
console.log('children5')
// 16 resolve把Promise的状态改为fullfilled,//这里的children6会放入res中
resolve('children6')
},0)
//6微任务,先不执行。
// 7.then是需要Promise中有result,然后才可以添加到.then中,但setTimeout是一个宏任务,所以回调没有添加到微任务队列中去
}).then((res)=>{
//14 打印
console.log('children7')
// 15 宏任务执行
setTimeout(()=>{
// 17 打印
console.log(res)
},0)
})
/*
* start
* children4
* 第一轮宏任务结束,接下来清空微任务队列,但没有微任务
* 尝试执行下一轮宏任务
* children2
* 第二轮宏任务结束,接下来清空微任务队列,
* children3
* 尝试执行第三轮宏任务
* children5
* children7
* children6
* */
const p = function () {
//1 宏任务
return new Promise((resolve, reject) => {
// 2 宏任务
const p1 = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(1)
},0)
resolve(2)
})
// 3 放入微任务队列
p1.then((res)=>{
console.log(res)//2
})
// 4 打印
console.log(3)
resolve(4)
})
}
// 5 放入微任务队列
p().then((res)=>{
console.log(res)//4
})
// 4 打印
console.log('end')
/*
*3
* end
* 2
* 4
* */
2 事件 捕获和冒泡(应用场景:事件委托)
捕获:自顶而下,window -> 目标元素
目标阶段
冒泡:自底向上,目标元素 -> window
- window.addEventListener 监听的是什么阶段的事件?
冒泡阶段
window.addEventListener('click',()=>{
})//默认这里第三个参数是false
捕获阶段
window.addEventListener('click',()=>{
},true)
- 案例 : 在历史界面上,添加一个属性,banner = true, 不再响应原理的函数,提示alter 解决方法:在最顶层绑定一个捕获事件,去拦截冒泡,
window.addEventListener('click',()=>{
if(banned){
e.stopPropagation()//阻止捕获
}
},true)
- 应用场景:事件委托
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<ul id="ul">
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
<li>1</li>
</ul>
</body>
<script type="text/javascript">
const ul = document.querySelector('ul')
ul.addEventListener('click',function (e) {
const target = e.target
if (target.tagName.toLowerCase() === 'li') {
const liList = this.querySelectorAll('li')
const index = Array.prototype.indexOf.call(liList,target)
alert(`内容是${target.innerHTML},索引${index}`)
}
})
</script>
</html>
3 节流、防抖
- 函数节流:设置一个时间间隔,如果一个频繁触发的事件发生大于该时间间隔,就执行,否则不执行。(应用在 resize 、scorll事件)
节流 : 是确保函数特定的时间内至多执行一次。
(对比防抖:是函数在特定的时间内不被再调用后执行。input事件)
- 时间戳的写法,第一次立即执行
function throttle(callback,wait) {
//定义开始时间
let start = 0
// 返回结果是一个函数
return function (e) {
//判断时间间隔,获取当前时间戳
let now = Date.now()
//判断
if (now - start >= wait) {
//如果满足判断,则执行回调函数
callback.call(this,e)//this指向返回事件源
//修改开始时间
start = now
}
}
}
- 第一次(最后一次)不立即执行的写法
function throttle(fn,interval){
let timer = null
return funtion(){
let context = this
if(!timer){
timer = SetTimeout(funtion(){
fn.apply(context,arguments)//这里的arguments是指传入的参数
timer = null
},interval)
}
}
}
- 第一次不立即执行的写法,最后一次立即执行的写法
funtion throttle(fn,delay){
let timer = null
let start = Date.now()
return funtion(){
let curTime = Date.now()
let remainTime = delay - (cutTime - start)//剩余时间= 完整间隔时间- 已经过去的时间
let context = this
cleanTimeout(timer)//要保证每次进入,都要重新计算remainTime
if(remainTime <= 0){
fn.apply(context,arguments)
start = Date.now()
}else{
timer = setTimeout(fn,remainTime)
}
}
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script type="text/javascript" src="./函数节流.js"></script>
<style>
body{
height: 2000px;
background: linear-gradient(paleturquoise,pink);
}
</style>
</head>
<body>
<script type="text/javascript">
//绑定滚动事件
window.addEventListener('scroll',throttle(function (e) {
// console.log(Date.now())
console.log(e)
},500))
</script>
</body>
</html>
- 函数防抖:一个事件 如果在一段时间内没有再次触发 就执行,;如果再次触发,就再等一会。(应用在 input 关键字搜索事件)
是函数在特定的时间内不被再调用后执行。
function debounce(callback,time) {
//定时器变量
let timeId = null
//返回一个函数
return function (e) {
//判断
if (timeId !== null) {
//清空定时器
clearTimeout(timeId)
}
//启动定时器
timeId = setTimeout(()=>{
//执行回调
callback.call(this,e)//这里回调函数的this是外层的this
},time)
}
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script type="text/javascript" src="./函数防抖.js"></script>
</head>
<body>
<input type="text">
<script type="text/javascript">
let input = document.querySelector('input')
// input.onkeydown = function (e) {
// console.log(e.keyCode)
// }
input.onkeydown = debounce( function (e) {
console.log(e.keyCode)
},500)
</script>
</body>
</html>
4 Promise
promise 在实例化时,就执行了。.then 或者await 是为了拿到值
//Promise 方法
new Promise((resolve, reject) => {
console.log('1')
setTimeout(()=>{
resolve('2')
},1000)
}).then(res => {
console.log(res)
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve( {msg:'3'})
},1000)
})
},err=>{
console.log(err)
}).then(res=>{
console.log(res.msg)
}
)
- 静态方法: Promise.all() Promise.race() Promise.resolve() Promise.reject()
- (1) Promise.resolve()
static resolve(value) {
if (value instanceof Promise) {
// 如果是Promise实例,直接返回
return value;
} else {
// 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED
return new Promise((resolve, reject) => resolve(value));
}
}
- (2) Promise.reject()
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason);
})
}
- (3) promiss.all Promise.all是支持链式调用的,本质上就是返回了一个Promise实例,通过resolve和reject来改变实例状态。
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
【返回一个promise对象,只有当所有promise都成功时返回的promise状态才成功,需要注意的点是:
1所有的promise状态变为FULFILLED,返回的promise状态才变为FULFILLED。
2一个promise状态变为REJECTED,返回的promise状态就变为REJECTED。
3数组成员不一定都是promise,需要使用Promise.resolve()处理。】
function PromiseAll(promiseArray) {
return new Promise((resolve, reject) => {
//判断传入的参数是否是 []
if (!Array.isArray(promiseArray)) {
return reject(new Error('传入的参数得是数组'))
}
const res = []
const len = promiseArray.length
//计数器
let counter = 0
for (let i = 0; i < len.length; i++) {
Promise.resolve(promiseArray[i]).then(value => {
counter++
res[i] = value
// 如果全部执行完,返回promise的状态就可以改变了
if (counter === len) {
resolve(res)
}
}).catch(err=>reject(err))
}
})
}
//测试
const p1 = new Promise((res,rej)=>{
setTimeout(()=>{
res('1')
},1000)
})
const p2 = new Promise((res,rej)=>{
setTimeout(()=>{
res('2')
},2000)
})
const p3 = new Promise((res,rej)=>{
setTimeout(()=>{
res('3')
},3000)
})
const proAll = PromiseAll([p1,p2,p3])
.then(res =>
console.log(res)
)
.catch((err)=>
console.log(err)
)
- (4) promiss.race Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
Promise.race = function(promiseArr) {
return new Promise((resolve, reject) => {
promiseArr.forEach(p => {
// 如果不是Promise实例需要转化为Promise实例
Promise.resolve(p).then(
val => resolve(val),
err => reject(err),
)
})
})
}
- 装饰器 调用一个请求(常量),多个地方都会请求,但没有全局的状态管理,就可以用 Promise,
const cacheMap = new Map()
function enableCache(target,name,describe) {
const cal = describe.value
describe.value = async function (...args) {
const cacheKey = name + JSON.stringify(args)
if (!cacheKey.get(cacheKey)) {
const cacheValue = Promise.resolve(val.apply(this.args)).catch(_=>{
cacheMap.set(cacheKey,null)
})
cacheMap.set(cacheKey,cacheValue)
}
return cacheMap.get(cacheKey)
}
return describe
}
class PromiseClass {
@enableCache
static async getInfo(){
}
}
PromiseClass.getInfo()
PromiseClass.getInfo()
PromiseClass.getInfo()
PromiseClass.getInfo()
5 前端加载优化 + 并发处理
页面性能检测:developers.google.com/speed/pages… Polyfill:polyfill.io/v3/url-buil… (这个cdn 对 Polyfill 按需加载,减小体积)
- 优化的一个重要的点:首屏时间(vue的白屏时间)
减少页面加载时间 =>减少页面加载资源 的具体方法:
- 1 请求当前需要的资源:
- 异步加载,图片懒加载(延迟加载)【两个方法】,polyfill(ES高版本 转为低版本的ES,目的是为了版本适配), 方法一:
鼠标滚动后再加载图片,先监听鼠标滚动事件
(有两个高度:窗口显示高度window.innerHeight;
图片到视窗上边的距离,用元素的getBoundingClientRect().top来获取),
如果图片可以看见也就是 getBoundingClientRect().top < window.innerHeight ,反之,则大于。
即 每次滚动 判断 图片到视窗上边的距离 < 窗口显示高度,
(多次触发,浪费资源,不好)
window.addEventListener('scroll',(e)=>{
imgs.forEach(img =>{
const imgTop = img.getBoundingClientRect().top
if(imgTop < window.innerHeight){
//获取 html 中自定义属性 data-src
const data_src = img.getAttribute('data-src')
img.setAttribute('src',data_src)//将原img的src属性 赋值为data_src
}
console.log('scroll 触发次数')
})
})
方法二:
图片懒加载: IntersectionObserver 是 chrome 浏览器 提供的构造函数 观察到目标元素和可视窗口的交叉区域
// observer.observe(DOM 节点)//图片未加载,观察
// observer.unobserve(DOM 节点)//图片加载了,就取消观察
//获取图片标签
const imgs = document.querySelectorAll('img')
const observer = new IntersectionObserver(callback )//回调函数触发两次:1 看见了触发 2 看不见了触发
// 传入的 entries 是数组
const callback = entries => {
entries.forEach( entry =>{
if (entry.isIntersecting) {//isIntersecting这个属性可以判断这次触发回调函数是否已经观察到图片,如果是true 就是已经观察到了
const img = entry.target
// 获取 html 中自定义属性 data-src
const data_src = img.getAttribute('data-src')
img.setAttribute('src',data_src)//将原img的src属性 赋值为data_src
observer.unobserve(img)//图片加载了,就取消观察
}
})
}
imgs.forEach(img =>{
observer.observe(img)
})
- 2 缩减资源体积
-
打包压缩 如 Webpack、
-
尽量压缩cookie 的体积 (用户登录态要保留,其他的可以考虑压缩)
-
gizp(压缩静态js css 等资源的算法),
-
图片格式的优化,压缩(tinypng.com,可以压缩80%)、webp图片格式(先要判断浏览器是否支持webp,在将图片转为webp) 阿里云oss支持拼参数 ,将图片格式转webp ,需要注意什么?
-
// 判断兼容性
function checkwebp() {
try{
return(
document.createElement('canvas')//创建canvas元素,将图片转成base64,base63的开头携带图片的格式
.toDataURL('img/webp')//这里尝试将 图片格式 转成 webp
.indexOf('data:image/webp')===0 //如果开头有'data:image/webp' ,就支持webp
)
}catch (e) {
return false
}
}
const supportWebp = checkwebp()
//接下来是字符串的拼接,但边界条件要仔细考虑,
export function getWebpImageUrl(url) {
if (!url) {
throw Error('url 不能为空')
}
//判断是否是 base64 格式,如果是'date'开头,则返回
if (url.startsWith('date')) {
return url
}
if (!supportWebp) {
return url
}
//接下来是字符串的拼接
return url + '?x-oss-processxxxx'
}
-
3 时序优化(通过一些逻辑的设计,去优化资源)
- js里的promise.all 去并行(并发)的发请求
- ssr ,可以做缓存,把资源渲染好,打包,缓存在了csn文件上(),seo
- prefetch ,prerender,preload
<link rel='dns-prefetch' href='xxxx.com'>立马去dns上进行预解析
<link rel='preconnect' href='xxxx.com'>立马去预连接这个网址
<link rel='preload' as='image' href='xxxx.com/p.png'>立马去 预加载 图片
- 4 合理的利用缓存
-
cdn: cdn预热(cdn商 提前将信息 分发到结节上) cdn刷新(强制刷新)
-
如果一段js执行时间非常长,怎么分析是哪个函数(用 装饰器 计算函数执行时间 的方法)
-
export function measure(target:any,name:string,descriptor:any){
const oldValue = descriptor.value
descriptor.value = async function () {
console.time(name)
const ret = await oldValue.apply(this,arguments);
console.timeEnd(name)
return ret
}
return descriptor
}
- 页面 有 巨量图片,除了懒加载,有没有 限制图片的加载 (通过 promise 控制 并发 limit: Promise.race 竞速+ p.then 链式调用,去保证顺序推入)
function limitLoad(urls,handler,limit) {
const sequence = [].concat(urls)//原数组拷贝
let promises = []
//拿前几个url元素,并且改变原数组,也就是原数组去掉了这几个元素
promises = sequence.splice(0,limit).map((url,index) =>{
// map 函数是将原url 转换为另一个格式
return handler(url).then(()=>{
return index
})
})
//通过 Promise.race 竞速 ,找到最快执行完成的url
let p = Promise.race(promises)
for (let i = 0; i <sequence.length ; i++) {
//通过 p.then 链式调用,去保证顺序推入, 这里的res是 找到最快执行完成的url 的 index
p = p.then((res)=>{
//将 index 重新赋值后,再次竞速
promises[res] = handler(sequence[i]).then(()=>{
return res
})
return Promise.race(promises)
})
}
}
const urls = []
//测试
function loadImg(url) {
return new Promise((resolve, reject) => {
console.log('----'+url.info + 'start')
setTimeout(()=>{
console.log(url.info +'ok~')
resolve()
},url.time)
})
}
limitLoad(urls,loadImg,3)//传入的并发是3个
6 项目中用户状态(登录)
首先要调取登录接口
在前后端完全分离的情况下,Vue项目中实现token验证大致思路如下:
1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码
2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token
3、前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面
4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面
5、每次调后端接口,都要在请求头中加token
6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401
7、如果前端拿到状态码为401,就清除token信息并跳转到登录页面
调取登录接口成功,会在回调函数中将token存储到localStorage和vuex中
- 微信获取用户基本信息
- 用户未授权(首次登陆)
- 有一个button,是 open-type=‘getUserInfo’
- 用户已经授权(再次登陆)
- wx.getUserInfo
7 webpack的loader和plugin区别
对于loader,它是一个转换器,将A文件进行编译形成B文件,这里操作的是文件,比如将A.scss转换为A.css,单纯的文件转换过程
plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务
webpack的loader顺序: loader的执行顺序和配置中的顺序是相反的
8 跨域
1. 同源策略
同源策略(Same-Origin Policy)是浏览器的一种安全策略。
同源: 协议、域名、端口号 必须完全相同。
如:
同协议:如都是http或者https
同域名:如都是baidu.com/a 和baidu.com/b
同端口:如都是80端口
违背同源策略就是跨域。
【简言之,浏览器发出的请求url,与其所在页面的url不一样。此时,同源机制会让浏览器拒收 服务器响应回来的数据。】
2. 如何解决跨域
- JSONP
在网页有一些标签天生具有跨域能力,比如:img link iframe script。
JSONP 就是利用 script 标签的跨域能力来发送请求的。
- JSONP 的使用
1.动态的创建一个 script 标签
var script = document.createElement("script");
2.设置 script 的 src,设置回调函数
script.src = "http://localhost:3000/testAJAX?callback=abc"; function abc(data) {
alert(data.name); };
3.将 script 添加到 body 中
document.body.appendChild(script);
4.服务器中路由的处理
router.get("/testAJAX" , function (req , res) {
console.log("收到请求");
var callback = req.query.callback; var obj = {name:"孙悟空",
age:18 }
res.send(callback+"("+JSON.stringify(obj)+")"); });
复制代码
jsonp缺点:只能实现get一种请求。JSONP是野路子,不如下面的CORS正统。
- CORS
CORS 全称是 跨域资源共享(Cross-Origin Resource Sharing),是官方的跨域解决方
案,
CORS 是通过设置一个响应头来告诉浏览器,该请求允许跨域,主要是服务器端的设置:
router.get("/testAJAX" , function (req , res) {
//通过 res 来设置响应头,来允许跨域请求
//res.set("Access-Control-Allow-Origin", "http://127.0.0.1:3000");
res.set("Access-Control-Allow-Origin","*");
res.send("testAJAX 返回的响应");
});
浏览器收到该响应 以后就会对响应放行。
1 当使用 XMLHttpRequest 发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个响应头:Origin;(浏览器无需做设置 只需发送ajax)
2 后台服务器收到请求后,会进行一系列处理,如果确定接受请求,则在返回结果中加入一个响应头:Access-Control-Allow-Origin (换言之,服务器允许的域url,会加入此响应头,相当于一个凭证);
3 浏览器判断该相应头中是否包含 Origin 的值,如果有,则浏览器会处理响应,我们就可以拿到响应数据。
- Node中间件代理(两次跨域)
实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。 代理服务器,需要做以下几个步骤:
-
接受客户端请求 。
-
将请求 转发给服务器。
-
拿到服务器 响应 数据。
-
将 响应 转发给客户端。
- nginx 反向代理
实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。
使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
9 深拷贝和浅拷贝
值类型:number,string,boolean 引用类型:object
他们的赋值不同,值类型是在栈内存中直接赋值,而引用类型是在栈内存中将地址值赋给它,对象内容却存在堆内存中。
通过拷贝来实现 引用类型 的赋值.
浅拷贝Object.assign;
Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象
var b = Object.assign({},a)
Object.defineProperty(Object, 'assign', {
value: function(target, ...args) {
if (target == null) {
return new TypeError('Cannot convert undefined or null to object');
}
// 目标对象需要统一是引用数据类型,若不是会自动转换
const to = Object(target);
for (let i = 0; i < args.length; i++) {
// 每一个源对象
const nextSource = args[i];
if (nextSource !== null) {
// 使用for...in和hasOwnProperty双重判断,确保只拿到本身的属性、方法(不包含继承的)
for (const nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
// 不可枚举
enumerable: false,
writable: true,
configurable: true,
})
深拷贝
way3:工具库 lodash中有clone,和CloneDeep
先引入第三方工具,在克隆
var a = {name:'A',score:[99,100]}
var b = _.deepclone(a)//赋值
way2: json 的序列化成字符串,在反序列化为对象,但需要注意,对象中有属性是funtion就不能被序列化为字符串,就不可以用这种方法。
var b = JSON.parse(JSON.stringify(a))
way1:深拷贝(递归,需要考虑 循环引用递归,需要判断层数,跳出循环)
const cloneDeep1 = (target, hash = new WeakMap()) => {
// 对于传入参数处理
if (typeof target !== 'object' || target === null) {
return target;
}
// 哈希表中存在直接返回
if (hash.has(target)) return hash.get(target);
const cloneTarget = Array.isArray(target) ? [] : {};
hash.set(target, cloneTarget);
// 针对Symbol属性
const symKeys = Object.getOwnPropertySymbols(target);
if (symKeys.length) {
symKeys.forEach(symKey => {
if (typeof target[symKey] === 'object' && target[symKey] !== null) {
cloneTarget[symKey] = cloneDeep1(target[symKey]);
} else {
cloneTarget[symKey] = target[symKey];
}
})
}
for (const i in target) {
if (Object.prototype.hasOwnProperty.call(target, i)) {
cloneTarget[i] =
typeof target[i] === 'object' && target[i] !== null
? cloneDeep1(target[i], hash)
: target[i];
}
}
return cloneTarget;
}
****
1 数据类型& 判断方法
- javaScript 有两种数据类型:
- 基本(值)类型
- string: 任意字符串
- number: 任意的数字
- boolean: true/false
- undefined: undefined
- null: null
- 对象(引用)类型
- Object: 任意对象
- Function: 一种特别的对象(可以执行)
- Array: 一种特别的对象(数值下标, 内部数据是有序的)
- 判断
- typeof:
- 可以判断: undefined/ 数值 / 字符串 / 布尔值 / function
- 不能判断: null与object object与array
- ===
- 不能判断: undefined, null
var a,b
a = null
console.log(typeof a,a==='null')// 'object' false
b = undefined
console.log(typeof b,a==='undefined')// undefined false
- instanceof:
- 判断对象的具体类型,用来判断 A 是否为 B 的实例
var b1 = {
b2: [1, 'abc', console.log],
b3: function () {
console.log('b3')
return function () {
return 'fangyuan'
}
}
}
console.log(b1 instanceof Object, b1 instanceof Array) // true false
console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) // true true
console.log(b1.b3 instanceof Function, b1.b3 instanceof Object) // true true
console.log(typeof b1.b2, '-------') // 'object'
console.log(typeof b1.b3==='function') // true
console.log(typeof b1.b2[2]==='function')//false
console.log(b1.b3()()) //b3 fangyuan
- Object.prototype.toString
- toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString运行时this指向的对象类型, 返回的类型格式为[object,xxx],xxx是具体的数据类型,基本上所有对象的类型都可以通过这个方法获取到。
console.log(Object.prototype.toString.call(''))//[object String]
console.log(Object.prototype.toString.call(123))//[object Number]
console.log(Object.prototype.toString.call(true)) //[object Boolean]
console.log(Object.prototype.toString.call(undefined))//[object Undefined]
console.log(Object.prototype.toString.call(null))//[object Null]
console.log(Object.prototype.toString.call(new Function()))//[object Function]
console.log(Object.prototype.toString.call([1,2,4]))//[object Array]
引用变量赋值
- 2个引用变量指向同一个对象,通过一个变量修改对象内部的数据,其它变量看到的是修改后的数据。
var obj1 = {name:'tom'}
var obj2 = obj1
obj2.age = 12
console.log(obj1.age)//12
function fn(obj){
obj.name = 'lily'
}
fn(obj1)
console.log(obj2.name)//lily
- 2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一个变量依然指向前一个对象
var a = {age:12}
var b = a
a = {name:`sam`,age: 18}
console.log(b.age,a.name,a.age)//12 sam 18
function fn2(obj) {
obj = {age:15}
}
fn2(a)
console.log(a.age) //18
//函数也是对象
var a = 3
function fn(b) {
b = b + 1
}
fn(a)
console.log(a)//3
2 对象
对象:多个数据的封装体,一个对象代表现实中的一个事物,属性值是函数叫方法
函数 :具有特定功能的封装体,只有函数可以执行; 提高代码复用;便于阅读 函数声明
test()
obj.test()
new test()
test.call/apply(obj) //可以让一个函数称为指定任意对象的方法进行调用
var obj = {}
function test2() {
this.xxx = 'july'
}
//obj.test2() 不能直接调用,更本就没有
test2.call(obj) //可以让一个函数称为指定任意对象的方法进行调用
console.log(obj.xxx)
(1) new
1 以ctor.prototype为原型创建一个对象。
2 执行构造函数并将this绑定到新创建的对象上。
3 判断构造函数执行返回的结果是否是引用数据类型,若是则返回构造函数执行的结果,否则返回创建的对象。
function newOperator(ctor, ...args) {
if (typeof ctor !== 'function') {
throw new TypeError('Type Error');
}
const obj = Object.create(ctor.prototype);
const res = ctor.apply(obj, args);
const isObject = typeof res === 'object' && res !== null;
const isFunction = typeof res === 'function';
return isObject || isFunction ? res : obj;
}
(2) 构造函数创建对象
(1)使用工厂方法创建对象:
通过该方法可以大批量的创建对象,使用的构造函数都是Object。
所以创建的对象都是Object这个类型,就导致我们无法区分出多种不同类型的对象
(2)因此使用构造函数创建对象:
* 构造函数和普通函数的区别就是调用方式的不同
* 普通函数是直接调用,而构造函数需要使用new关键字来调用
* 使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。
* 我们将通过一个构造函数创建的对象,称为是该类的实例
-
构造函数的执行流程:
* 1.一旦出现new,立刻创建一个新的对象 * 2.将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象 * 3.逐行执行函数中的代码 * 4.将新建的对象作为返回值返回
3 this
* 解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this,
this指向的是一个对象,这个对象我们称为函数执行的 上下文对象,
根据函数的调用方式的不同,this会指向不同的对象:
*1.以函数的形式调用时,this永远都是window
*2.以方法的形式调用时,this就是调用方法的那个对象
*3 以构造函数的形式调用时,this是新创建的对象
*4 使用call 和 apply 调用时,this是指定的那个对象
var name = '全局'
function fun() {
console.log(this.name)
}
var obj = {
name:'tom',
sayName:fun
}
var obj2 = {
name:'lily',
sayName:fun
}
//根据函数的调用方式的不同,this会指向不同的对象
//1.以函数的形式调用时,this永远都是window
fun()// 全局;
//2.以方法的形式调用时,this就是调用方法的那个对象
obj.sayName()//tom;
obj2.sayName()//lily
如何确定this的值:
test(): window
p.test(): p
new test(): 新创建的对象
p.call(obj): obj
// call()和apply() 方法
function fun() {
alert(this.name)
}
var obj = {name:'obj',
sayName:function () {
alert(this.name)
}}
var obj2 = {name:'obj2'}
// fun.call(obj) //obj
// fun.apply(obj2.name)//obj2
// fun()//window
fun.call(obj,2,3)
fun.apply(obj,[2,3])
// obj.sayName.apply(obj2)
箭头函数和普通函数的区别?
箭头函数的this指向规则:
- 箭头函数没有prototype(原型),所以箭头函数本身没有this
- 箭头函数的this指向在定义的时候继承自外层第一个普通函数的this。
- 如果箭头函数外层没有普通函数,严格模式和非严格模式下它的this都会指向window(全局对象).
- 箭头函数本身的this指向不能改变,但可以修改它要继承的对象的this。
箭头函数的arguments:
1 箭头函数的this指向全局,使用arguments会报未声明的错误。
2 箭头函数的this指向普通函数时,它的argumens继承于该普通函数
箭头函数没有constructor:
使用new调用箭头函数会报错,因为箭头函数没有constructor
箭头函数不支持new.target
箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名
箭头函数相对于普通函数语法更简洁优雅
箭头函数的注意事项及不适用场景
箭头函数的注意事项:
箭头函数一条语句返回对象字面量,需要加括号
箭头函数在参数和箭头之间不能换行
箭头函数的解析顺序相对||靠前
不适用场景:箭头函数的this意外指向和代码的可读性。
var myObject = {
myName : 'JULY',
func:function () {
var self = this
console.log(this.myName)//JULY
console.log(self.myName);//JULY
(function () {
console.log(this.myName)//undefined
console.log(self.myName)//JULY
}());
(()=>{
console.log(this.myName
)//JULY
console.log(self.myName)//JULY
})();
}
}
myObject.func()
4 原型、原型链,继承
(1) 函数的prototype属性
原型就是一个对象,和其他对象没有任何区别,可以通过构造 函数来获取原型对象。 – 构造函数. prototype
// 原型prototype
console.log(Date.prototype, typeof Date.prototype)
function Fun() {
}
console.log(Fun.prototype)//原型对象,默认指向Object空对象
//给原型对象添加方法,给实例对象用
Fun.prototype.test = function () {
console.log('test()')
}
var fun = new Fun()
fun.test()
console.log(Fun.prototype)//{ test: [Function (anonymous)] }
console.log(Date.prototype.constructor === Date)//true
console.log(Fun.prototype.constructor === Fun)//true
//显式原型(Funtion的prototype) 和隐式原型(实例对象fun的__proto__)
//1 定义构造函数
function Fn() {//内部语句:this.prototype={}
}
console.log(Fn.prototype)
// 2 创建实例对象
var fn = new Fn()//内部语句: this.__proto__ =Fn.prototype
console.log(fn.__proto__)
// 对应的构造函数的显式原型 的值 = 对象的隐式原型 的值
console.log(Fn.prototype === fn.__proto__)//true,由于他们都保存相同的地址值
// 3 给构造函数的显式原型添加方法
Fn.prototype.test = function () {
console.log('test()')
}
// 4 通过实例调用原型的方法
fn.test()
(2) 原型链(隐式原型链)
- 访问一个对象的属性时,
-
先在自身属性中查找,找到返回
-
如果没有, 再沿着__proto__这条链向上查找, 找到返回
-
如果最终没找到, 返回undefined
原型对象也是对象,所以它也有原型,当我们使用一个对象的属性或方法时,会现在自身中寻找,自身中如果有,则直接使用,如果没有则去原型对象中寻找,如果原型对象中有,则使用,如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined
-
- 作用: 查找对象的属性(方法) 简单一点:就是利用原型让一个引用类型继承另一个引用类型的属性和方法;
function MyClass() {
}
MyClass.prototype.name = '我是原型中的名字'
var mc = new MyClass()
mc.age = 12
console.log(mc.name)//我是原型中的名字
console.log('name' in mc)//true
console.log(mc.hasOwnProperty('age'))//true
console.log(mc.hasOwnProperty('hasOwnProperty'))//false
console.log(mc.__proto__.hasOwnProperty('hasOwnProperty'))//false
console.log(mc.__proto__.__proto__.hasOwnProperty('hasOwnProperty'))//true
//原型链:本质上是隐式原型链
console.log(Object.prototype.__proto__)
function Fn() {
this.test1 = function () {
console.log('test1')
}
}
Fn.prototype.test2 = function () {
console.log('test2()')
}
var fn = new Fn()
fn.test1()
fn.test2()
console.log(fn.toString())
console.log(fn.test3)//undefined
//fn.test3()//TypeError: fn.test3 is not a function
//1 函数的显式原型默认指向空的Object的实例对象(但Object不满足)
console.log(Fn.prototype instanceof Object)//true
console.log(Object.prototype instanceof Object)//false
console.log(Function.prototype instanceof Object)//true
//2 所有函数都是Function的实例(Function也是Function的实例)
console.log(Function.__proto__ === Function.prototype)
//3 Object的原型对象是原型链的尽头
console.log(Object.prototype.__proto__)//null
//练习1
function Fn(){
}
Fn.prototype.a = 'xxx'
var fn1 = new Fn()
console.log(fn1.a)//xxx
var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn1.a,fn2.a)//xxx yyy
//练习1.5 注意:读取对象的属性值会查找原型链,但设置对象的属性值时,不会查找原型链
function Person(name,age){
this.name = name
this.age = age
}
Person.prototype.setName = function(name){
this.name = name
}
var p1 = new Person('tom',12)
p1.setName('bob')
console.log(p1)//Person { name: 'bob', age: 12 }
var p2 = new Person('jack',12)
p2.setName('lily')
console.log(p2)//Person { name: 'lily', age: 12 }
console.log(p1.__proto__ === p2.__proto__)//true
//练习 2
Object.prototype.foo = 'Object'
Function.prototype.foo = 'Function'
function Animal() {
}
var cat = new Animal()
console.log(cat.foo)//Object
console.log(Animal.foo)//Function
//练习 3
var b ={x:4}
function fn2(o) {
this.x = o.x
}
fn2.prototype = {
init:function () {
return this.x
}
}
var fn3 = new fn2({x:5})
var c = fn3.init()
console.log(fn3.init.call(b))//4
console.log(c)//5
// A(实例对象)instanceof B(构造函数)
function Foo() {}
var f1 = new Foo()
console.log(f1 instanceof Foo)//true
console.log(f1 instanceof Object)//true
console.log(Object instanceof Function)//true
console.log(Object instanceof Object)//true
console.log(Function instanceof Function)//True
console.log(Function instanceof Object)//true
function Foo() {}
console.log(Object instanceof Foo)//false
/*测试题1 */
var A = function() {
}
A.prototype.n = 1
var b = new A()
//b 到此为止
A.prototype = {
n: 2,
m: 3
}
var c = new A()
console.log(b.n, b.m, c.n, c.m)
//答案为1,undefined,2,3。
/* 测试题2 */
var F = function(){};
Object.prototype.a = function(){
console.log('a()')
};
Function.prototype.b = function(){
console.log('b()')
};
var f = new F();
f.a() //a()
f.b()// 报错f.b is not a function
F.a()//a()
F.b()//b()
(3) 原型继承
//继承
class father {
constructor() {
this.name = 'jack'
this.age = 30
}
sayName(){
return this.name
}
static saySex(){
return 'male'
}
}
class child extends father{
constructor() {
super();
this.name = 'tony'
this.age =2
}
sayAge(){
console.log(this.age)//undefined
}
}
let cc = new child()
let ff = new father()
console.log(cc.sayName())//tony
console.log(cc.sayAge())//2
console.log(father.saySex()) //male
// console.log(cc.saySex())
// console.log(ff.saySex())
person类继承的时候,怎么实现原型链的
function Parent() {
this.name = 'parent';
}
function Child() {
Parent.call(this);
this.type = 'children';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
5 执行上下文
(1) 变量提升
var a = 3
function fn() {
console.log(a)//undefined
var a = 4
}
fn()
console.log(b)//undefined
fn2()//可以调用,函数提升
fn3()//不可调用,变量提升
var b =3
function fn2() {
console.log('fn2()')//fn2()
}
var fn3 = function () {
console.log('fn3()')
}
(2) 全局执行上下文
console.log(a1,window.a1)
window.a2()
console.log(this)
var a1 =2
function a2() {
console.log('a2()')
}
(3) 函数执行上下文(N+1原则) //函数执行上下文(N+1原则),是封闭的,虚拟的,存在与栈中,只有执行函数体,才有函数执行上下文,但提前准备好了;函数调用结束后就自动释放
function fn(a1) {
console.log(a1)//2
console.log(a2)//undefine
a3()//a3()
console.log(this)//window
console.log(arguments)//伪数组(2,3)
var a2 =3
function a3() {
console.log('a3()')
}
}
fn(2,3)
执行顺序
// 1 进入window全局执行上下文
var a = 10
var bar = function (x) {
var b = 5
foo(x+b)//3.进入foo函数执行上下文
}
var foo = function (y) {
var c = 5
console.log(a+c+y)
}
bar(10)//2 进入bar函数执行上下文
console.log('global'+i)//undefined
var i = 1
foo(1)
function foo(i){
if(i==4){
return
}
console.log('foo() begin'+i)//1 2 3
foo(i+1)//递归调用:在函数内部调用自己
console.log('foo() end'+i)//3 2 1
}
console.log('global'+i) //1
//test1:先变量提升,再函数提升
function a() {}
var a
console.log(typeof a)//function
//test2
if(!(b in window)){
var b = 1
}
console.log(b)//undefined
//test3
var c= 1
function c(c) {
console.log(c)
}
c(2)// 报错 ,c不是函数,变量提升了
(4) 作用域(N+1 原则)与作用域链 首先来几题热热身,搞清楚全局作用域、函数作用域
var a = 123
function fun1() {
alert(a)//123; 函数作用域没有a,到全局作用域找到a
}
fun1()
var b = 123
function fun2() {
alert(b) // undefined; 所以函数作用域中 b 是undefined (2)
var b = 456 //首先,var b = 456 提前声明了 b (1)
}
fun2()
alert(b) //123;这里是全局作用域中找b (3)
var c = 123
function fun3() {
alert(c) // 123; 首先,函数作用域里,目前没有c,到全局作用域找c (1)
c = 456 // 这里c是全局的,因为没有 var 所以是 window.c ;也就是给全局的c重新赋值456 (2)
}
fun3()
alert(c)// 456; 由于 c 重新赋值了456 (3)
var d = 123
function fun4(d) {// 首先,这里有形参d,相对于函数作用域中 var d (1)
alert(d) // undefined; 函数作用域里,目前d没有赋值 (2)
d = 456 // 这里d 赋值为456 (3)
}
fun4()
alert(d)// 123; 这里是全局作用域中的d (4)
var e = 123
function fun5(e) {// 首先,这里有形参e,相对于函数作用域中 var e(1)
alert(e) // 123; 函数作用域中 var e = 123(3)
e = 456 // 这里e是函数的,给函数的e重新赋值456(4)
}
fun5(123)//然后,这里实参123,赋值 e = 123(2)
alert(e) //123;这里是全局作用域中找e(5)
作用域是静态的,(全局作用域、函数作用域、块作用域),用来隔离变量,不同作用域下同门变量不会有冲突
var a = 10,
b = 20
function fn(x) {
var a = 100
c = 300
console.log('fn()',a,b,c,x)//fn() 100 20 300 10
function bar(x) {
var a = 1000,
d = 400
console.log('bar()',a,b,c,d,x)//bar() 1000 20 300 400 100 //bar() 1000 20 300 400 200
}
bar(100)
bar(200)
}
fn(10)
//作用域链
var a = 1
function fn1() {
var b = 2
function fn2() {
var c = 3
console.log(c)//3
console.log(b)//2
console.log(a)//1
//console.log(d)// 保存,未定义
}
fn2()
}
fn1()
//test1
var x= 10
function fn() {
console.log(x)
}
function show(f) {
var x = 20
f()
}
show(fn)
var fn =function () {
console.log(fn)//[Function: fn]
}
fn()
//test2
var obj ={
fn2:function () {
console.log(fn2)//保存
console.log(this.fn2)//[Function: fn2]
}
}
obj.fn2()
6 闭包
闭包:函数嵌套,内部函数引用外部函数的数据,并执行外部函数
通俗的讲就是函数a的内部函数b,被函数a外部的一个变量引用的时候,就创建了一个闭包。
什么情况下会用到闭包吗? 最常见的是函数封装的时候,再就是在使用定时器的时候,会经常用到...
function fn1() {
var a = 2
var b = 3
function fn2() {//执行函数定义,就会产生闭包(不用调用内部函数)
console.log(a)
}
}
fn1()//执行外部函数
(1) 闭包的应用
1将函数作为另一个函数的返回值
function fn1() {
var a = 2//此时闭包就已经产生了,函数定义执行和函数执行是两回事(由于函数提升,内部函数对象已经创建了)
function fn2() {//创建闭包函数
a++
console.log(a)
}
return fn2
}
var f = fn1()
f()//3 执行函数
f()//4
f = null //闭包死亡,(包含闭包的函数对象称为垃圾对象)
2将函数作为实参传递给另一个函数调用
function showDelay(meg,time) {
setTimeout(function () {
alert(msg)//闭包
},time)
}
showDelay('july',2000)
3.定义JS模块,将所有的函数功能封装到一起
function myModule() {
var msg ='July'
//操作数据的行为
function doSomething() {
console.log('doSomething()'+msg.toUpperCase())
}
function doOtherthing() {
console.log('doOtherthing()'+msg.toLowerCase())
}
//向外暴露对象
return {
doSomething:doSomething,
doOtherthing:doOtherthing
}
}
//way2 更好,可以直接加载到window上。可以直接使用
(function (window) {
var msg = 'July'
//操作数据的行为
function doSomething() {
console.log('doSomething()'+msg.toUpperCase())
}
function doOtherthing() {
console.log('doOtherthing()'+msg.toLowerCase())
}
// //向外暴露对象
window.myModule2 = {
doSomething:doSomething,
doOtherthing:doOtherthing
}
})(window)
//test1
var name = 'the window'
var object = {
name:'my object',
getNameFunc:function () {
return function () {//无闭包
return this.name
};
}
};
console.log(object.getNameFunc()())//the window
//test2
var name2 = 'the window'
var object2 = {
name2:'my object',
getNameFunc:function () {
var that = this
return function () {//有闭包
return that.name2
}
}
};
console.log(object2.getNameFunc()())//my object
//test3
function fun(n,o) {
console.log(o)
return{
fun:function (m) {
return fun(m,n)//调用的是fun(n,o),有闭包,使用了n,传给o
}
};
}
var a = fun(0);a.fun(1);a.fun(2);a.fun(3);//undefined ,0,0,0
var b = fun(0).fun(1).fun(2).fun(3)//undefined ,0,1,2
var c = fun(0).fun(1);c.fun(2);c.fun(3)//undefined ,0,1,1
简单写一个闭包吧:
function a(){
var i=0;
function b(){
i++;
alert(i);
}
return b;
}
var c = a();
c();//?
c();//?
c();//?应聘者:应该是会依次弹出1,2,3。
原理: i是函数a中的一个变量,它的值在函数b中被改变,函数b每执行一次,i的值就在原来的基础上累加 1 。因此,函数a中的i变量会一直保存在内存中。当我们需要在模块中定义一些变量,并希望这些变量一直保存在内存中但又不会 “污染” 全局的变量时,就可以用闭包来定义这个模块。
闭包的用处: 它的最大用处有两个,一个是它可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
//创建数组元素
var num = new Array();
for(var i=0; i<4; i++){
//num[i] = 闭包;//闭包被调用了4次,就会生成4个独立的函数
//每个函数内部有自己可以访问的个性化(差异)的信息
num[i] = f1(i);
}
function f1(n){
function f2(){
alert(n);
}
return f2;
}
num[2](); //2
num[1](); //1
num[0](); //0
num[3](); //3
(2) 闭包的优缺点:
优点:
① 减少全局变量;
② 减少传递函数的参数量;
③ 封装;
缺点:
① 使用闭包会占有内存资源,过多的使用闭包会导致内存溢出等
解决方法:
简单的说就是把那些不需要的变量,但是垃圾回收又收不走的的那些赋值为null,然后让垃圾回收走;