预编译
js代码在浏览器执行的的时候,会有一个预编译过程。
预编译过程做了哪些事情?
- 创建了ao对象
- 找形参和变量的声明并赋值undefined
- 找函数声明,会覆盖变量的声明
注意:函数作用域中创建的是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
深拷贝和浅拷贝
- 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性时基本类型,拷贝的就是基本类型的值,如果属性时引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
- 深拷贝是将一个对象存内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且改变新对象不会影响原有对象。
案例
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
由主栈、宏任务、微任务三部分组成
必须要等主栈中的所有代码执行完清空后,再执行微任务最后是宏任务
- fetch、setTimeout、setInterval、ajax等异步操作都属于宏任务
- promise async await 等属于微任务
案例
单例模式
指只有一个实例,可以全局访问
主要解决一个全局使用的类,频繁的创建和销毁的问题
优点是:内存中只有一个实例,减少了内存的开销
使用场景:
- 全局缓存
- 弹窗
案例
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