前端技术总结
预编译
function fn(a,c){
console.log(a) //f a(){}
var a = 123
console.log(a) //123
console.log(c) //f c(){}
function a(){}
if(false){
var d=678
}
console.log(d)//undefined
console.log(b)//undefined
var b =function() {}
console.log(b)//f(){}
function c(){}
console.log(c)//f c(){}
}
fn(1,2)
//预编译
//作用域的创建阶段 预编译阶段
//预编译的时候做了哪些事情
//js的变量对象 AO对象 供js引擎自己去访问的
//1 创建了ao对象 2 找形参和变量的声明 作为ao对象的属性名 值为undefined 3 实参和形参相统一 4找函数 声明 会覆盖变量的声明
AO:{
a:undefined 1 function a(){}
c:undefined 2 function c(){}
d:undefined
b:undefined
}
this指向问题
在函数中直接使用
function get(content){
console.log(content)
}
get('您好')//可以看作下列语句的语法糖
get.call(window,'您好')
函数作为对象的方法被调用(谁调用 就指向谁)
var person = {
name:'张三',
run:function(time){
console.log(`${this.name}在跑步 最多${time}min就不行了`)
}
}
person.run(30)//可以看作下列语句的语法糖
person.run.call(person,30)
列题
var name=222
var a ={
name:111,
say:function(){
console.log(this.name)
}
}
var fun = a.say
fun() //fun,call(window) 222
a.say()//a.say.call(a) 111
var b={
name:333,
say:function(fun){
fun()
}
}
b.say(a.say)//传入之后等价于 fun()=a.say 等价于第一种 222
b.say = a.say
b.say()//b调用 等于console.log(this.name)复制到b的say方法中 即333
箭头函数中的this
-
箭头函数中的this是在定义函数的时候绑定的,而不是在执行函数的时候绑定
-
箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为他没有this,所以也就不能用作构造函数
var x =11 var obj={ x:22, say:()=>{ console.log(this.x) } } obj.say() //输出是11var obj={ birth:1990, getAge:function(){ var b=this.birth var fn=()=>new Date().getFullYear()-this.birth return fn() } } console.log(obj.getAge())//输出是2021-1990=31因为箭头函数在getAge()中定义的,其父级为obj内部作用域,所以其箭头函数中的this指向的是父级obj对象,this.birth即1990
js中的深浅拷贝
赋值 :当我们把一个对象赋值给一个新的变量时候,赋的其实是该对象在栈中的地址,而不是堆中的数据,也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此两个对象是联动的
浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响
深拷贝:从堆中开辟一块新的区域存放对象,原始对象和新对象之前互不影响
| 操作 | 和原数据是否指向同一对象 | 第一层数据为一般数据类型 | 第一层数据不是一般数据类型 |
|---|---|---|---|
| 赋值 | 是 | 改变会使得原始数据改变 | 改变会改变原始数据 |
| 浅拷贝 | 否 | 改变不会改变原始数据 | 改变会改变原始数据 |
| 深拷贝 | 否 | 改变不会改变原始数据 | 改变不会改变原始数据 |
-
赋值代码
var person = { name:"张三", hobby:['学习','敲代码','吃瓜'] } var person1 = person person1.name = "李四" person1.hobby[0]='玩耍' console.log(person)//都是name:"李四" hobby:['玩耍','敲代码','吃瓜'] console.log(person1) -
浅拷贝代码(基本数据类型不受影响,引用数据类型被同时改变)
var person = { name:"张三", hobby:['学习','敲代码','吃瓜'] } function shallowCopy(obj){ var target = {} for(var i in obj){ if(obj.hasOwnProperty(i)){ target[i]=obj[i] } } return target } var person1 = shallowCopy(person) person1.name='王二' person1.hobby[0]="玩耍" console.log(person)//张三 玩耍 敲代码 吃瓜 console.log(person1)//王二 玩耍 敲代码 吃瓜 -
深拷贝代码(拷贝前后数据互不影响)
var person = { name:"张三", hobby:['学习','敲代码','吃瓜'] } function deepClone(obj){ var cloneObj = new obj.constructor() if(obj===null) return obj if(obj instanceof Date) return new Datr(obj) if(obj instanceof RegExp) return new RegExp(obj) if(typeof obj !== 'object') return obj for(var i in obj){ if(obj.hasOwnProperty(i)){ cloneObj[i]=deepClone(obj[i]) } } return cloneObj } // var person1 = JSON.parse(JSON.stringify(person))//大多数时候可以,但是如果对象中有日期、正 则表达式、函数就不行 var person1 = deepClone(person) person1.name="胡图图" person1.hobby[0]="打麻将" console.log(person1)//二者完全不影响 console.log(person)浅拷贝的实现方式
- lodash里面的_.cloneObj
- ...展开运算符
- Array.prototype.concat()
- Array.prototype.slice()
深拷贝的实现方式
- JSON.parse(JSON.stringify()) 在对象中有正则表达式、Date对象、正则对象、Promise时会发生异常
- 递归实现(如上图中代码)
- cloneDeep()
- jquery.extend()
防抖
可以采用闭包的方式
var input = document.querySelector('input')
function debounce(delay){
let timer
return function(value){
clearTimeout(timer)
timer=setTimeout(function(){
console.log(value)
},delay)
}
}
var debounceFunc = debounce(1000)
input.addEventListener('keyup',function(e){
debounceFunc(e.target.value)
})
采用闭包的方式,return的函数中既可以使用timer,timer只会在调用debounce的时候生成一次
节流
一段时间内只做一件事情,无论点击多少次,等到执行完了之后再执行第二个事情,闭包解决节流
document.querySelector('button').addEventListener('click',thro(handle,2000))
function thro(func,wait){
let timeout
return function(){
if(!timeout){
timeout = setTimeout(function(){
func()
timeout=null
},wait)
}
}
}
function handle(){
console.log(Math.random())
}
闭包的底层原理
function a(){
var aa=123
function b(){
var bb = 234
console.log(aa)
}
return b
}
var res = a()
res()//能够输出123
因为a()执行完毕之后a对应的作用域链会断开,但是b在定义的时候就可以访问到a作用域内的作用域链,这个作用域链并不会断开,所以res()依然可以访问到aa=123,即可以输出123,闭包可以让变量一直保存在内存中。
闭包实现单例模式
var createLogin = function () {
var div = document.createElement('div')
div.innerHTML = "我是弹出的div"
div.style.display = 'none'
document.body.appendChild(div)
return div
}
var getSingle = function(fn){
var result
return function(){
return result || (result=fn.apply(this,arguments))
}
}
var create = getSingle(createLogin)
document.querySelector('button').onclick=function(){
var loginLay = create()
loginLay.style.display = "block"
}
无论点击多少次,只创建一个div
js运行机制
js是单线程,因为js离不开和用户的操作,假定js为多线程,在操作dom的时候,同时创建删除同一个dom,是无法执行的
arguments
function get(){
console.log(arguments)
}
get(1,2,3)
//arguments是一个类数组对象
//可以通过Array.prototype.slice.call(arguments)来将其转化为数组
//也可以通过es6中的展开运算符来转化为数组
为什么在调用这个函数的时候,代码中的b会变成全局变量
function func(){
let a=b=3
}
func()
console.log(b)//3
console.log(a)//error
因为let a=b=3等价于let a=(b=3) b并没有声明,所以在创建的时候会变成全局变量,在func()执行之后,因为b为全局变量,所以可以访问,但是a在func()的作用域内,外部并不能访问
哪些操作会造成内存泄漏
- 闭包
- 意外的全局变量
- 被遗忘的定时器
- 脱离dom的引用(比如var div=document.querySelector('div')之后将其删除,但是并未对消除div这个引用,在内存中依然保留了对于div的引用)
高阶函数
将函数作为参数或者返回值的函数
function highOrder(params,callback){
return callback(params)
}
手写map
var arr=[1,2,3]
function map(arr,mapCallback){
//检查参数是否正确
if(!Array.isArray(arr)||!arr.length||typeof mapCallback!=="function"){return []}
else{
let result = []
for(let i=0;i<arr.length;i++){
result.push(mapCallback(arr[i],i,arr))//1每一项 2索引 3传入数组
}
return result
}
}
var res = map(arr,(item)=>{
return item*2
})
console.log(res)//2,4,6
js的事件循环机制
-
js中的异步操作比如fetch setTimeout setInterval压入到调用栈中的时候里面的消息会进入到消息队列中去,消息队列会等到调用栈清空之后再执行
-
promise async await的异步操作的时候会加入到微任务中去,会在调用栈清空的时候立即执行,调用栈中加入的微任务会立马执行
js单例模式
- 定义 1.只有一个实例 2.可以全局访问
- 主要解决的问题:一个全局使用的类 频繁的创建和销毁
- 何时使用:当你想控制实例的数目 节省系统化资源的时候
- 如何实现:判断系统是否有这个单例,如果有则返回,没有则创建
- 单例模式的优点:内存中只有一个实例 减少了内存的开销 尤其是频繁的创建和销毁实例(比如首页页面的缓存)
es6创建单例
class Person{
constructor(name,sex,hobby){
this.name = name
this.sex = sex
this.hobby = hobby
}
static getInstance(name,sex,hobby){
if(!this.instance){
this.instance = new Person(name,sex,hobby)
}
return this.instance
}
}
let person1 = Person.getInstance('胡图图','男','吃瓜')
let person2 = Person.getInstance('胡英俊','女','打豆豆')
console.log(person1==person2)//true
console.log(person1)//'胡图图','男','吃瓜'
console.log(person2)
策略模式
- 策略模式的定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
- 策略模式指的是定义一系列的算法,把它们一个个封装起来。将不变的部分和变化的部分分隔是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来
var registerForm = document.querySelector('.registerForm')
var strategies = {
isNonEmpty: function(value,errorMsg){
if(value == ''){return errorMsg}
},
minLength:function(value,length,errorMsg){
if(value.length<6){return errorMsg}
},
isMobile:function(value,errorMsg){
if(!/^1[3|5|5][0-9]{9}$/.test(value)){return errorMsg}
}
}
//假设有一个验证类,Validator new Validator()
var validataFun = function(){
var validator = new Validator()
//添加验证规则
validator.add(registerForm.username,'isNonEmpty','用户名不能为空')
validator.add(registerForm.password,'minLength:6','密码长度不能小于6位')
validator.add(registerForm.username,'isMobile','手机号格式不正确')
//开启验证
var errorMsg = validator.start()
return errorMsg
}
registerForm.onsubmit = function(){
var errorMsg = validataFun()
if(errorMsg){
alert(errorMsg)
return false
}
}
//封装策略类 构造函数class
var Validator = function(){
//保存验证规则的数组
this.cache=[]
}
Validator.prototype.add = function(dom,rule,errorMsg){
var arr = rule.split(':')
this.cache.push(function(){
var strategy = arr.shift()
arr.unshift(dom.value)
arr.push(errorMsg)
return strategies[strategy](...arr)
})
}
Validator.prototype.start = function(){
for(var i=0,vaFunc;vaFunc = this.cache[i++];){
var msg = vaFunc()
if(msg){
return msg
}
}
}
BFC
-
定义:块级格式化上下文,它是指一个独立的块级渲染区域,只有Block-level BOX参与,该区域拥有一套渲染规则来约束块级盒子的布局,且与区域外部无关
从一个现象说起
一个盒子father没有设置height,其内容子元素son都为浮动的时候,无法撑起自身,即father的height一直为0,这个盒子没有形成BFC
-
如何创建BFC
- float的值不为none
- position的值不是static或者relative
- display的值是inline-block、flex或者inline-flex
- overflow:hidden(比较好的方式,不会影响到外部的布局)
-
BFC的其他作用
- 可以取消margin塌陷的问题
- 可以阻止元素被浮动元素覆盖
数组扁平化
-
数组自带的扁平化方法
const arr = [1,[2,[3,[4,5]]],6] console.log(arr.flat(Infinity)) -
正则加JSON.parse(JSON.stringify(arr))
-
递归