JS 记录

作用域的说明

指一个变量的作用范围

全局作用域说明

  1. 在页面打开是创建,页面关闭时销毁
  2. 编写在 script 标签中的变量和函数,作用域为全局,在页面的任意位置都可以被访问到
  3. 在全局作用域中有全局对象 window,代表一个浏览器窗口,由浏览器创建,可以直接调用
  4. 全局作用域中声明的变量和函数会作为 window 对象的属性和方法保存

函数作用域说明

  1. 调用函数时被创建,执行完被销毁
  2. 每次调用都会创建一个新的函数作用域,并且相互独立
  3. 函数作用域中科院访问到全局作用域的变量,但在函数外无法访问函数作用域内的变量
  4. 函数作用域访问变量、函数过程, 自身作用域 > 上一级作用域/全局作用域

作用域

1、当函数代码执行的前期,会创建一个执行期上下文的内部对象 AO(作用域)
2、内部的对象是预编译的时候创建出来的,当函数被调用的时候,会先进线预编译
3、在全局代码执行的前期会创建一个执行期的上下文对象 GO

函数作用域预编译

  1. 创建 AO 对象, AO{}
  2. 找形参和变量声明,将变量和形参名当做 AO 对象的属性名,值为 undefined
  3. 实参形参相统一
  4. 在函数体里面找函数声明
			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)

执行结果
执行结果

全局作用域的预编译(同上)

  1. 创建 GO 对象
  2. 找变量声明,将变量名作为 GO 对象的属性,值为 undefined
  3. 找函数声明,值赋予函数体

深浅拷贝

数据类型

基本数据类型:

  1. number(数字类型的变量或值)
  2. string(字符串类型的变量或值)
  3. boolean(布尔类型的变量或值)
  4. undefined(未定义的变量或值)
  5. null(空)

引用数据类型:

  1. 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))

将函数作为一个对象,把功能拆开为 计算 和 使用

发布订阅模式

发布-订阅模式又叫观察者模式,它定义对象的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于他的对象都将得到通知

作用

  1. 支持简单的广播通信,当对象状态发生改变时,自动通知已经订阅过的对象
  2. 应用再异步编程中,代替回调函数,可以订阅 ajax 之后的事情,只需要订阅自己需要的部分
  3. 对象之间的松耦合
  4. 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内容)

重点

  1. 块级格式化上下文
  2. 独立的块级渲染区域
  3. 该区域拥有一套渲染规则来约束块级盒子的布局,且与区域外部无关

出现的问题

一个盒子不设置 height ,当内容子元素都浮动时,无法撑起自身。(没有形成BFC)

创建BFC

  1. float 的值不是 none (父元素)
  2. position 的值不是 static 或者 relative
  3. display 的值不是 inline-block 或者 inline-flex
  4. overflow:hidden

BFC的其他作用

  1. BFC 可以取消盒子 margin 塌陷 (子元素设置margin,作用在了父元素)
  2. BFC 可以组织 元素被浮动元素覆盖

参考文档

参考链接及大部分内容来自
js常考题/跨域/浏览器工作原理/Vue/React/性能优化
Object. hasOwnProperty方法
JS中的typeof用法
面试题:深拷贝和浅拷贝(超级详细,有内存图)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值