JavaScript原理解刨

预编译

js代码在浏览器执行的的时候,会有一个预编译过程。
预编译过程做了哪些事情?

  1. 创建了ao对象
  2. 找形参和变量的声明并赋值undefined
  3. 找函数声明,会覆盖变量的声明
    在这里插入图片描述

注意:函数作用域中创建的是AO对象,而全创建是GO对象

案例

在这里插入图片描述
结果为

// 依次输出  
undefinede
123 
function  
undefined  
undefined 
function b
function c

在这里插入图片描述

this指向

this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象

案例

var name = 222
var a = {
  name: 111,
  say: function(){
    console.log(this.name)
  }
}
var fun = a.say
fun() // 相当于 window.fun() 所以这里输出 222 (调用全局的name)
a.say() // this指向a 输出 111
var b = {
  name: 333,
  asy: function(fn){
    fn() // 这里还是指向window 所以输出 222
  }
}
b.say(a.say) // 222
b.say = a.say
b.say() //333

箭头函数中的this

箭头函数中的this是在定义函数的时候绑定的,而不是在执行函数的时候绑定
箭头函数没有自己的this,所以会导致他的this会指向他的上一级。
因为没有this,不能同new关键字来创建函数,不能用作构造函数。

案例

var x = 11
var obj = {
  x: 22,
  say:()=>{
    console.log(this.x)
  }
}
obj.say(); // 输出 11

深拷贝和浅拷贝

  1. 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性时基本类型,拷贝的就是基本类型的值,如果属性时引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
  2. 深拷贝是将一个对象存内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且改变新对象不会影响原有对象。

案例

let obj = {
	name: '小红',
	hobby: ['跑步', '游泳', {
		port: '打球'
	}]
}

//赋值   只是重新克隆了一份地址 还是只想同一个堆
// let obj1 = obj
// obj1.name = '小白'

// console.log('obj',obj);
// console.log('obj1',obj1);   //输出结果一致


//浅克隆
// function clone(obj){
// 	let obj1 = {}
// 	for (let var1 in obj) {
// 		if(obj.hasOwnProperty(var1)){
// 			obj1[var1] = obj[var1] 
// 		}
// 	}
// 	return obj1
// }

// let obj1 = clone(obj)
// obj1.name='袭击'
// obj1.hobby[0] = '散步'
// console.log('obj',obj);
// console.log('obj1',obj1);   //散步全部影响到了


//深克隆   使用递归的方式
function deepClone(obj){
	let obj1 = new obj.constructor
	if(obj===null) return obj
	if (obj instanceof Date) return new Date(obj)
	if (obj instanceof RegExp) return new RegExp(obj)
	if(typeof obj !=='object') return obj
	for (var var1 in obj) {
		if(obj.hasOwnProperty(var1)){
			obj1[var1] = deepClone(obj[var1])
		}
	}
	return obj1
}
let obj1 = deepClone(obj)
obj1.name='袭击'
obj1.hobby[0] = '散步'
console.log('obj',obj);
console.log('obj1',obj1);  //所有更改后的结果并不影响

在这里插入图片描述

js自带实现方法


// deepClone() 实现深克隆
//$.extend() 深克隆
let obj1 = [1,2,3,4,5,[1,24,87],[1,57,87]]
//let oi = [...obj1] //浅克隆

 //let oi = Object.assign(obj1)
 
 let oi = deepClone(obj1)
 oi[5][0] = 3
console.log(oi);
console.log(obj1);

防抖函数

当函数持续触发事件,一定时间内没有再触发事件,事件函数只执行一次,如果规定时间内又触发了该事件就重新开始延迟。

案例

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<input type="text" name="" id="input" value="" />
		<script type="text/javascript">
			var ipt = document.getElementById('input')
			
			function fun (value){
				let time
				return function(value){
					clearTimeout(time)
					time = setTimeout(function(){
						console.log(value.target.value);
					},1000)
				}
			};
			ipt.addEventListener('keyup',fun(ipt.value)) //监听键盘抬起事件  
            这里是通过闭包来控制定时器开关处理函数执行,一秒之后才触发函数输出输入框中的结果,如果一秒之内又触发在重新定时
		</script>
	</body>
</html>

节流函数

指在一定时间内某个事件函数只会被触发一次

案例

<input type="button" name="" id="btn" value="请点我" />
var btn = document.getElementById('btn')
			btn.onclick = function() {
				//console.log('s');
				let time

				return function() {
					//console.log('s');
					if (!time) {
						time = setTimeout(function() {
							console.log('这是两秒后的输出');
							time = null
						}, 2000)
					}
				}
			}()   //2秒内不管点击多少次,控制台只会打印一次 '这是两秒后的输出'

手写map

在这里插入图片描述

event-loop

由主栈、宏任务、微任务三部分组成
必须要等主栈中的所有代码执行完清空后,再执行微任务最后是宏任务

  1. fetch、setTimeout、setInterval、ajax等异步操作都属于宏任务
  2. promise async await 等属于微任务

案例

在这里插入图片描述

单例模式

指只有一个实例,可以全局访问
主要解决一个全局使用的类,频繁的创建和销毁的问题
优点是:内存中只有一个实例,减少了内存的开销
使用场景:

  1. 全局缓存
  2. 弹窗

案例

ES5实现单例模式

在这里插入图片描述

ES6实现单例模式

在这里插入图片描述

发布-订阅模式

发布订阅模式主要分为三个模块:订阅者、发布者、处理中心
比如现在我有一个需求,有一个卖家和有两个买家需要到卖家手里,一个需要买一条黑色的鞋子,一个需要买白色的鞋,当卖家当拿到货后需要通知不同的买家。这里我们就可以通过发布订阅的模式来完成需求。

案例

//定义发布者
var shoeObj = {}
shoeObj.list = []

//增加订阅者
shoeObj.listen = function(key,fun) {
	if(!this.list[key]){
		this.list[key] = []
	}
	this.list[key].push(fun)
}


//发布消息
shoeObj.trigger = function() {
	var key = Array.prototype.shift.call(arguments)
	var fns = this.list[key]
	//console.log(fns);
	if(!fns || fns.length==0){
		return false
	}
	for (let i = 0,fn;fn = fns[i++];) {
		fn.apply(this,arguments)
	}
}
shoeObj.listen('red',function(color, size) {
	console.log(`颜色是${color},大小是${size}`);
})

shoeObj.listen('whit',function(color, size) {
	console.log(`再次颜色是${color},再次大小是${size}`);
})
shoeObj.trigger('re', '红色',11)
shoeObj.trigger('whit', '白色',21)

reduce详解

语法:

let arr = [1,2,5,7]
arr.reduce((prev,cur,index,arr)=>{
},init)
// prev----表示上一次回调时的返回值,第一次为init初始值
// cur----表示当前正在处理的数组元素
// index----表示当前正在处理的数组元素的索引,第一次,若设置了init值,则索引为0,否则为1
// init 表示初始值

案例

数组去重

// 数组去重
let arr = [1,8787,1,888,888,2,3,4,8,4,5,5,5,5,5]

const result =(arr)=>{
	return arr.reduce((pre,cur)=>{
		if(pre.indexOf(cur)==-1){
			 pre.push(cur)
		}
		return pre
	},[])
}
console.log(result(arr));

求和

let arr = [1,8787,1,888,888,2,3,4,8,4,5,5,5,5,5]

const result =(arr)=>{
	return arr.reduce((pre,cur)=>{
		return pre+cur
	})
} //由于并没有设置初始值  所以第一循环时 pre=1 cur=8787

console.log(result(arr));

数组扁平化

意思是指将一个多维数组转换为低维数组

实现方式

递归实现

let arr = [1,[2,3,[4,5,[8,[10,[9]]]]],6,[7]]
let new_arr = []
function fn(arr){
	for (let i = 0; i < arr.length; i++) {
		if(Array.isArray(arr[i])){
			fn(arr[i])
		}else{
			new_arr.push(arr[i])
		}
	}
	return new_arr
}
let arr2 = fn(arr)
console.log(arr2);

flat实现

//数组扁平化
let arr2 = [[[[[[[[[[[[[[[[[[[[22]]]]]]]]]]]]]]]]]]]]
console.log(arr2.flat(Infinity)); //参数可以是具体数子

reduce实现

let arr = [1,[2,3,[4,5,[8,[10,[9]]]]],6,[7]]
const result =(arr)=>{
	return arr.reduce((pre,cur)=>{
		return pre.concat(Array.isArray(cur)?result(cur):cur)
	},[])
}
console.log(result(arr));

call、apply、bind

区别

call和apply返回的是一个立即执行函数,而bind返回的是一个可执行函数 call的参数必须是逐个的,而apply的参数是一个数组

实际应用

将伪数组转换为数组

let obj ={
	0:1,
	1:'小明',
	2:30,
	length:3  //必须要有
}
console.log(Array.prototype.slice.call(obj));
// apply 使用
// 将 arr2 的数组元素加入到arr1中
let arr1 = [1,2,3,4]
let arr2 = [5,6]

Array.prototype.push.apply(arr1,arr2)

includes

判断是否存在某个值

const a = ['1','2',3,'panda']
if(a.includes(3)){
	console.log(a);  //这里会打印结果
}
if(a.includes(1)){
	console.log(a);  //这里不会打印结果
}

Javacript的优化

用对象的字面量代替switch

const a = {
	yellow:['colors'],
	red:['reds']
}
function isColor (color){
	return a[color] || []
}
console.log(isColor('red'));

数组新增的every、some

every检测每一个元素是否都存在某个值,若存在则返回true
some检测元素中是否存在某个值,若存在返回true

案例

const a = [{name:'张三',color:'red'},{name:'李四',color:'red'}]

let isRed=(a)=>{
	let isTrue = a.every(f=>f.color=='red')
	return isTrue
	
}
console.log(isRed(a))

let isName=(a)=>{
	let isZs = a.some(f=>f.name=='张三')
	return isZs
}
console.log(isName(a));

Object.defineProperty()

语法:

Object.defineProperty(obj, prop, descriptor)

参数:

obj
要定义属性的对象。
prop
要定义或修改的属性的名称或 Symbol 。
descriptor
要定义或修改的属性描述符。

属性描述:

1、configurable - 表示此属性能否被delete,默认false;
2、enumerable - 表示此属性能否被枚举,默认为false;
3、value - 设置此属性对应的值,默认为undefined;
4、writable - 设置value属性能否被修改值,为true时方可被改变,默认为false;
5、get - 给属性提供getter方法,默认为undefined,访问该属性时,该方法会被执行,默认参数为this对象;
6、set - 给属性提供setter方法,默认为undefined,属性值修改时,会执行该方法,唯一参数为新的值;

// 对obj对象已有的name属性添加数据描述
let obj = {name:'张三'}
Object.defineProperty(obj, 'name', {
  configurable: true | false,
  enumerable: true | false,
  value: '任意类型的值', //对name添加属性描述
  writable: true | false
});
console.log(obj.name) //输出 '任意类型的值'
var obj ={}
Object.defineProperty(obj,'name',{
	get:function(){
		return name
	},
	set:function(val){
		name = '111'
	}
})

obj.name='小明'
console.log(obj.name);

Proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)

语法

const p = new Proxy(target, handler)

参数

target
要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
在这里插入图片描述

案例

模拟双向数据绑定

<input type="text" name="" id="input" value="" />
		<p></p>
		<script type="text/javascript">
			var input = document.getElementById('input')
			var p = document.getElementsByTagName('p')[0]
			let obj = {}
			let newProxy = new Proxy(obj, {
				get: function(target, key, recevier) {
					console.log(target, key, recevier);
				},
				set: function(target, key, value, recevier) {
					p.innerText=value
					return Reflect.set(target,key,value,recevier);
				}
			})
			
			window.addEventListener('keyup',(e)=>{
				newProxy.text = e.target.value
				console.log(obj.text);
			})

无操作转发代理

let target = {};
let p = new Proxy(target, {});

p.a = 37;   // 操作转发到目标

console.log(target.a);    // 37   操作已经被正确地转发

Reflect

Reflect是ES6为了操作对象而新增的API

Reflect.get(target, name, receiver)

该方法是用来读取一个对象的属性。
参数如下解析:
target: 目标对象
name: 是我们要读取的属性。
receiver(可选): 可以理解为上下文this对象。

var obj ={name:'张三',age:32}
console.log(Reflect.get(obj,'name'));  //张三
console.log(Reflect.get(obj,'name2'));  // undefined
var ob ={name:'李四'}
var obj ={name:'张三',age:32,get dd(){console.log(this.name);}}


console.log(Reflect.get(obj,'dd',ob))  //先打印李四,因为没有返回值所以再打印undefined 
//调用dd会自动执行函数   因为第三个参数指使this指向了ob对象

Reflect.set(target,name,value,receiver)

set就是设置该对象的属性值
target: 我们需要操作的对象。
name: 我们需要设置该对象的属性名。
value: 我们要设置的属性值。
receiver: 可以理解为上下文this对象。如果我们在设置值的时候遇到setter函数,该参数就指向与setter中上下文this对象。


var obj = {
	name:'张三',
	age:22
}

console.log(Reflect.set(obj,'hobby',['篮球','乒乓球']));  // 输出 true   //如果该对象属性值设置成功会返回bool值true
console.log(obj);  //  打印 { name: '张三', age: 22, hobby: [ '篮球', '乒乓球' ] }

Reflect.apply(target,thisArg,args)

通过指定的参数列表对该目标函数的调用
target: 我们的目标函数.
thisArg: target函数调用的时候绑定的this对象。
args: 就是函数参数列表。

// 获取数组中的最大值
let ab = [24,58,1,5,888,555,454,5,69]
let result = Reflect.apply(Math.max,ab,ab)
console.log(result);

// ES5 写法 
let result1 = Math.max.apply(ab,ab)
console.log(result1);

Reflect.has(target,name)

检查一个对象上是否含有特定的属性

let ac = {
	a:'ss',
	b:'dd'
}

console.log(Reflect.has(ac,'s'));  //false

Reflect.defineProperty(target,name,desc)

该方法与Object.defineProperty方法类似的,不过唯一的区别是 Reflect.defineProperty返回值是一个Boolean的值


const obj = {};
const res = Reflect.defineProperty(obj, 'b', {
  configurable: true,
  enumerable: true,
  value:'111'
});

console.log(res); // true
console.log(obj); // 111
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值