作用域的说明
指一个变量的作用范围
全局作用域说明
- 在页面打开是创建,页面关闭时销毁
- 编写在 script 标签中的变量和函数,作用域为全局,在页面的任意位置都可以被访问到
- 在全局作用域中有全局对象 window,代表一个浏览器窗口,由浏览器创建,可以直接调用
- 全局作用域中声明的变量和函数会作为 window 对象的属性和方法保存
函数作用域说明
- 调用函数时被创建,执行完被销毁
- 每次调用都会创建一个新的函数作用域,并且相互独立
- 函数作用域中科院访问到全局作用域的变量,但在函数外无法访问函数作用域内的变量
- 函数作用域访问变量、函数过程, 自身作用域 > 上一级作用域/全局作用域
作用域
1、当函数代码执行的前期,会创建一个执行期上下文的内部对象 AO(作用域)
2、内部的对象是预编译的时候创建出来的,当函数被调用的时候,会先进线预编译
3、在全局代码执行的前期会创建一个执行期的上下文对象 GO
函数作用域预编译
- 创建 AO 对象, AO{}
- 找形参和变量声明,将变量和形参名当做 AO 对象的属性名,值为 undefined
- 实参形参相统一
- 在函数体里面找函数声明
function fn(a, c) {
console.log(a) // function a() {}
var a = 123
console.log(a) // 123
function a() {}
console.log(c) // function c() {}
if(false) {
var d = 111
}
console.log(d) // undefined
console.log(b) // undefined
var b = function() {}
console.log(b) // function() {}
function c() {}
console.log(c) // function c() {}
}
// 预编译作用域
// AO {
// 找形参和变量声明 函数声明
// a: undefined 123 function a() {}
// b: undefined function() {}
// c: undefined function c() {}
// d: undefined
// }
fn(1, 2)
执行结果
全局作用域的预编译(同上)
- 创建 GO 对象
- 找变量声明,将变量名作为 GO 对象的属性,值为 undefined
- 找函数声明,值赋予函数体
深浅拷贝
数据类型
基本数据类型:
- number(数字类型的变量或值)
- string(字符串类型的变量或值)
- boolean(布尔类型的变量或值)
- undefined(未定义的变量或值)
- null(空)
引用数据类型:
- object(对象类型的变量或值)
浅拷贝与深拷贝
- 浅拷贝是创建一个新对象,这个对象有着原始对象属性的一份精确拷贝
如果属性是基本类型,拷贝的则是基本类型
如果属性是引用类型,拷贝的就是存储地址
如果其中一个对象改变了这个地址,就会影响另一个对象 - 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,修改不影响原对象
浅拷贝、深拷贝 and 赋值
赋值
将一个对象赋值给一个新变量,赋的其实是该该对象在栈中的地址,而不是数据,两个对象共享同一个存储空间,无论哪个对象发生改变,都会影响存储空间的内容,两个对象是联动的
<script type="text/javascript">
var pr1 = {
name: 'pr1',
boxdata: ['1', '2', ['3', '4'], '5']
}
var pr2 = pr1
pr2.name = 'pr2'
pr2.boxdata[0] = 'pr2'
console.log(pr1)
console.log(pr2)
</script>
结果如图,基本数据和数组都发生了改变
浅拷贝
创建出了一个新的对象,在其中一个对象发生改变时,基础数据类型不发生改变,引用类型数据发生改变
<script type="text/javascript">
var pr1 = {
name: 'pr1',
boxdata: ['1', '2', ['3', '4'], '5']
}
// var pr2 = pr1
function shallowCopy(obj) {
var tag = {} // 创建一个变量
for(var i in obj) {
if(obj.hasOwnProperty(i)) {
tag[i] = obj[i]
}
}
return tag
}
var pr2 = shallowCopy(pr1)
pr2.name = 'pr2'
pr2.boxdata[0] = 'pr2'
console.log(pr1)
console.log(pr2)
</script>
结果如图,基本数据不变,数组发生改变
补充信息
hasOwnProperty()基本概念
hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中(非继承属性)是否具有指定的属性,
如果 object 具有带指定名称的属性,则 hasOwnProperty 方法返回 true,否则返回 false。此方法不会检查对象原型链中的属性;该属性必须是对象本身的一个成员。
hasOwnProperty信息来源
深拷贝
创建出了一个新的对象,在其中一个对象发生改变时,原对象不发生改变
<script type="text/javascript">
var pr1 = {
name: 'pr1',
boxdata: ['1', '2', ['3', '4'], '5']
}
function dellCopy(obj) {
var dell = {}
if(obj === null) return obj
if(obj instanceof Date) return new Date(obj)
if(obj instanceof RegExp) return new RegExp(obj)
// a instanceof b b 是不是在 a 的原型链上
if(typeof obj !== 'object') return obj
// 结束递归
for(var i in obj) {
if(obj.hasOwnProperty) {
dell[i] = dellCopy(obj[i])
}
}
return dell
}
var pr2 = dellCopy(pr1)
pr2.name = 'pr2'
pr2.boxdata[0] = 'pr2'
console.log(pr1)
console.log(pr2)
</script>
结果如图:原对象并没有发生改变
<script type="text/javascript">
var a = {
'name': '汪XX',
'sex': 'boy',
'live': {
'love': 'boy',
'everyday': '摸鱼'
},
'city': '长沙'
}
var b = {}
function obj(pra) {
let dataList = []
for(let data in pra) {
if(typeof pra[data] === 'object') { // 如果是 object ,递归
dataList[data] = obj(pra[data]) // 将函数的值赋给变量 dataList
} else { // 直接赋值
dataList[data] = pra[data]
}
}
return dataList
}
b = obj(a)
b.city = '北京'
b.live.love = 'gril'
console.log(a)
console.log(b)
</script>
这里感谢面试题:深拷贝和浅拷贝(超级详细,有内存图)提供的思路与帮助
a instanceof b 翻译:b 是不是在 a 的原型链上
闭包
内部的函数被保存到了外部执行
能让变量一直保存在内存之中,下面的防抖和节流都有使用
function fn() {
var a = 123
function b() {
var b = 99
console.log(a) // 能输出 a 是因为在定义的时候能访问到 a 的变量 (AO)
}
return b
}
var res = fn()
res()
案例
单例模式
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>单例模式</title>
</head>
<body>
<button id="clickBtn">点击</button>
<script type="text/javascript">
var createDiv = function(a,b,c) {
console.log(a,b,c) // arguments 接收的参数
var div = document.createElement('div') // 创建一个节点 div
div.innerHTML = '点击成功'
div.style.display = 'none' // 隐藏
document.body.appendChild(div) // 在指定元素节点的最后一个子节点之后添加节点
return div
}
var getBtn = function(fn) {
var result; // 定义一个变量
return function() { // 返回函数,在函数定义之初,就能获取到 getBtn 的 AO,拿到 result
return result || (result = fn.apply(this, arguments)) // 如果 result 为 undefined,执行后面,第二次 result 必然有值,直接返回 result
// 如果被调用的时候有参数回传,可以用 arguments 接收
}
}
var create = getBtn(createDiv) //调用时,返回一个函数,内部的函数,被保存到了外面
document.getElementById('clickBtn').onclick = function() {
var clkBtn = create(1,2,3) // 调用 create ,getBtn 中的 result 并没有被销毁
clkBtn.style.display = 'block'
}
</script>
</body>
</html>
防抖与节流
闭包的实际应用
防抖函数
持续触发事件,一段时间没有触发事件之后,处理函数事件才会触发一次
当设定的时间来临之前,又触发了事件,重新开始延时计时
没操作一段时间后做一件事
<script type="text/javascript">
// 获取到输入框
var ipt = document.getElementById('ipt');
// 防抖函数,传入时间值和函数
function list(times, fn) {
// 获取到 setTimeout 并执行
let timer
// 返回整个函数,使其一直在内存中,储存这个 timer 变量, 闭包
return function(data) {
// 清除上一个已执行的 计时器
clearTimeout(timer)
// 开始计时
timer = setTimeout(function() {
// 将返回结果传递到外部输出
fn(data)
}, times)
}
}
// 返回结果处理函数
function fn(data) {
console.log(data)
}
// 将函数打包赋值给 devlist, 拉出来额外传递参数,如时间 和 需要传递的函数名 ,先调用出来
var devlist = list(1000,fn)
// 获取需要防抖的内容
ipt.addEventListener('keyup', function(e) {
// 执行 devlist ,传递值
devlist(e.target.value)
})
</script>
节流
多次操作的情况下,只执行一次,等执行完之后,再执行下一次
<button id="btn">点击</button>
<script type="text/javascript">
function func(callback, wait){
// 目前 timeOut 的值为 undefined
let timeOut
// return 出这个函数,使其一直在内存当中
return function() {
// 当 timeOut 的值为 undefined 执行,否则执行其他操作
if(!timeOut){
// 为 timeOut 赋值,开始处理函数
timeOut = setTimeout(function(){
callback()
// 将 timeOut 清空,允许下一次开始
timeOut = null
},wait)
} else {
console.log('未执行')
}
}
}
function fn() {
console.log(Math.random())
}
document.getElementById('btn').onclick = func(fn,1000)
</script>
JS中的typeof用法
JS的运行机制
单线程语言,同一时间做同一事情
arguments 的对象是什么
类数组对象,没有数组的一些方法
<script type="text/javascript">
function get() {
console.log(arguments) // 类数组
// 转化为数组
console.log(...arguments) // ES6 展开运算符
console.log(Array.prototype.slice.call(arguments)) // ES5 因为arguments
}
get(1,2,3)
</script>
哪些操作会造成内存泄露
- 闭包
- 被遗忘的定时器
- 意外的全局变量
function () { let a = b = 0 }
由于 b 未定义,就在全局中创造了一个 b 变量 - 脱离DOM的引用
定义了一个 DOM 对象的操作,但是页面中删除了 DOM 对象
高阶函数
将函数作为参数或者返回值的函数,可以查看闭包的案例
event-loop 事件循环机制
详解
事件循环机制,由三部分组成
调用栈 - 微任务队列 - 消息队列
调用栈
event-loop 开始的时候,会从全局一行一行的执行,遇到函数调用时,会将函数调用压入调用栈中,被称之为 “帧”,当函数返回后从调用栈中弹出
消息队列
js 中的异步操作,如 fetch setTimeout setInterval 会进入消息队列中去,等到调用栈清空之后执行
微任务队列
promise async await 的一部操作会加入微任务队列 ,在调用栈清空时立即调用,调用栈中的微任务会立即执行
执行顺序
调用栈 > 微任务队列 > 消息队列
单例模式
单例模式的理解
定义:只有一个实例,可以全局的访问
主要解决: 一个全局使用的类,频繁的创建和销毁
何时使用:想控制实例的数目,节省系统化资源的时候
如何实现:判断系统是否有这个单例,如果有则返回,没有则创建
单例模式的优点:内存中只要一个实例,减少了内存的开销,尤其是频发的创建和销毁实例(首页的缓存)
使用场景:
1.全局的缓存
2.弹窗
创建一个普通的例
var getSinger = function() {
var div = document.createElement('div');
div.innerHTML = '我是单例';
div.style.display = 'none';
document.body.appendChild(div)
return div
}
document.getElementById('clkBtn').onclick = function() {
var a = getSinger()
a.style.display = 'block'
}
在点击之后就会创造一个 div ,但这不是单例,因为点击就创造了一个例,频繁的创建
修改成一个单例
这个时候,判断内存中是否有这个单例,如果有则返回,没有则创建
var getSinger = (function() { // var obj = ( function(){} )() 立即执行函数
var div
// 不被销毁 一直在内存中
return function() {
if(!div) {
div = document.createElement('div'); // 注意这里不要再 var 一个div
div.innerHTML = '我是单例';
div.style.display = 'none';
document.body.appendChild(div)
}
return div
}
})()
document.getElementById('clkBtn').onclick = function() {
var getFn = getSinger()
getFn.style.display = 'block'
}
将创建 div 的函数闭包,判断如果有这个 div,则 return 一个 div ,没有则执行
优化这个单例
单例的含义是只有一个实例,只做一件事情,单一职责,把 getSinger 拆开成创建和形成
// 单例的形成
var getSinger = function(fn) {
var result
return function() {
return result || (result = fn.apply(this, arguments)) // 注意第二个选择需要将值赋值给 result
}
}
// div 的形成
var getDivLayer = function() {
var div = document.createElement('div'); // 注意这里不要再 var 一个div
div.innerHTML = '我是单例';
div.style.display = 'none';
document.body.appendChild(div)
return div
}
// 执行
var createSinger = getSinger(getDivLayer)
document.getElementById('clkBtn').onclick = function() {
var getFn = createSinger()
getFn.style.display = 'block'
}
实现了单例的职责
策略模式
策略模式的定义
定义一系列算法,把他们封装起来,并且算法之间可以相互替换
核心:将算法的使用和算法的实现分离开
策略模式的案例
判断等级并给出相应的倍数
function bbc(a, salary) {
if(a = 's') {
return salary *4
}
if(a = 'a') {
return salary *3
}
if(a = 'b') {
return salary *2
}
}
console.log(bbc('s',8000))
if 循环的问题在于臃肿和弹性不足,每多一个判断就需要增加一个 if 语句,且从上到下都需要执行
策略模式
//
var strategies = {
's': function(salary) {
return salary * 4
},
'a': function(salary) {
return salary * 3
},
'b': function(salary) {
return salary * 2
}
}
var getBouns = function(level, salary) {
return strategies[level](salary)
}
console.log(getBouns('s', 8000))
将函数作为一个对象,把功能拆开为 计算 和 使用
发布订阅模式
发布-订阅模式又叫观察者模式,它定义对象的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于他的对象都将得到通知
作用
- 支持简单的广播通信,当对象状态发生改变时,自动通知已经订阅过的对象
- 应用再异步编程中,代替回调函数,可以订阅 ajax 之后的事情,只需要订阅自己需要的部分
- 对象之间的松耦合
- Vue react 之间的跨组件传值
案例
简单的发布订阅
// 定义发布者
var shoeObj = {}
// 存放函数的列表
shoeObj.list = []
// 增加订阅者
shoeObj.listen = function(fn) {
shoeObj.list.push(fn)
}
// 发布消息
shoeObj.trigger = function() {
// 遍历数组,执行函数
for(var i = 0, fn; fn = this.list[i++];) {
fn.apply(this, arguments)
}
}
// 发布触发
shoeObj.listen(function(color, size) {
console.log(`颜色是${color}`)
console.log(`尺码是${size}`)
})
shoeObj.trigger('红色', 37)
shoeObj.trigger('黑色', 47)
// 简单的发布订阅
绑定一个值 key,可以关注到每一个订阅
// 增加一个标识 key
// 定义发布者
var shoeObj = {}
// 存放函数的列表
shoeObj.list = []
// 增加订阅者
shoeObj.listen = function(key, fn) {
if(!this.list[key]) {
this.list[key] = [] // 如果没有 key ,则为空数组
}
this.list[key].push(fn) // 带 key 订阅
}
// 发布消息
shoeObj.trigger = function() {
// 取出 key
var key = Array.prototype.shift.call(arguments)
var fns = this.list[key]
if(!fns || fns.length == 0) {
return
}
// 遍历数组,执行函数
for(var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments) // ES5
// fn(...arguments) // ES6
}
}
shoeObj.listen('red', function(size) {
console.log(`小红订阅的尺码是${size}`)
})
shoeObj.listen('black', function(size) {
console.log(`小黑订阅的尺码是${size}`)
})
shoeObj.trigger('red', 37)
shoeObj.trigger('black', 47) // 每一个订阅者,都有一个唯一的标识。 black
然后封装一下
// 封装
var Event = (function() {
var list = {},
listen,
trigger,
remove;
listen = function(key, fn) {
if(!this.list[key]) {
this.list[key] = [] // 如果没有 key ,则为空数组
}
this.list[key].push(fn) // 带 key 订阅
}
trigger = function() {
// 取出 key
var key = Array.prototype.shift.call(arguments)
var fns = this.list[key]
if(!fns || fns.length == 0) {
return
}
// 遍历数组,执行函数
for(var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments) // ES5
// fn(...arguments) // ES6
}
}
remove = function(key, fn) {
var fns = this.list[key]
if(!key) {
return false;
}
if(!fn) {
fn && (fns.length = 0)
} else {
for(var i = fns.length - 1; i >= 0; i--) {
var _fn = fns[i]
if(_fn == fn) {
fns.splice(i, 1)
}
}
}
}
return {
listen,
trigger,
remove
}
})()
数组扁平化
数组扁平化是指一个多维数组变成一个一维数组
// 数组
const arr = [1, [2, 3, [4, [5]]], 6]
console.log(arr)
// flat(Infinity) Infinity 正无穷,如果知道有几层嵌套,可以直接写
console.log(arr.flat(Infinity))
console.log(arr.flat(3))
// reduce()
const flatten = arr => {
return arr.reduce((
(pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}
), [])
}
const res2 = flatten(arr)
console.log(res2)
// 函数递归
const res3 = [];
const fn = arr => {
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
fn(arr[i])
} else {
res3.push(arr[i])
}
}
}
fn(arr)
console.log(res3)
BFC( CSS内容)
重点
- 块级格式化上下文
- 独立的块级渲染区域
- 该区域拥有一套渲染规则来约束块级盒子的布局,且与区域外部无关
出现的问题
一个盒子不设置 height ,当内容子元素都浮动时,无法撑起自身。(没有形成BFC)
创建BFC
- float 的值不是 none (父元素)
- position 的值不是 static 或者 relative
- display 的值不是 inline-block 或者 inline-flex
- overflow:hidden
BFC的其他作用
- BFC 可以取消盒子 margin 塌陷 (子元素设置margin,作用在了父元素)
- BFC 可以组织 元素被浮动元素覆盖
参考文档
参考链接及大部分内容来自
js常考题/跨域/浏览器工作原理/Vue/React/性能优化
Object. hasOwnProperty方法
JS中的typeof用法
面试题:深拷贝和浅拷贝(超级详细,有内存图)