个人总结
事件API
-
Vue
.stop
阻止传播.prevent
阻止行为动作.capture
捕获。 添加事件监听器时使用事件捕获模式,即内部元素触发的事件先在此处理,然后才交由内部元素进行处理.self
仅对自身有效.once
事件仅发生一次.passive
被动,立即触发,比如绑定scroll,一滑动就会触发,而不是等到滑动结束。不能与.prevent同时使用,prevent会被忽略
-
React
-
原生
-
event.preventDefault()
阻止事件关联的动作,比如submit 就不会提交表单
-
event.stopPropagation()
阻止事件传播 比如冒泡
-
Vue
Vue3.0 组合API
把要用到的方法和属性放在一个api里,这样看起来更加方便整洁。如果像2.0中,一个逻辑的方法在methods中,属性在data和computed中,阅读起来需要上下翻找。
组件传参
-
父传子
//father <template> <div> <SonComponent :data-send="data"></SonComponet> </div> </template> <script> import SonComponent from './' export default { name : "father", components : { SonComponent }, data(){ return { data: 100 } } } </script> //son <template> <div> {{dataSend}} </div> </template> <script> export default { name : "SonComponent", props:{ dataSend: { type: String, default : 1 } } }
:data-send="data"
data是父组件中要传递的参数值,data-send在子组件中名字为dataSend,Vue会自动处理成驼峰形式
子组件需要在props中声明收到的参数
-
子传父
<SonComponent @sonEmit="fun" />
父组件在methods中定义fun函数,当子组件
this.$emit('sonEmit',参数)时会调用,并且传参
-
VueX - Store
定义
在store.js 的 state 中声明变量的默认值
在mutations 中 定义set函数
获取
this.$store.state.dataName
设置
this.$store.commit('函数名',参数)
Ref
<SonComponent ref="sonName" />
父组件可以通过this.$ref.sonName
来调用子组件的属性
v-if 和 v-show 的区别和应用场景
相同点:v-if 与 v-show 都可以动态控制 DOM 元素的显示隐藏。
不同点:
- 手段:v-if 是动态的向 DOM 树内添加或者删除 DOM 元素;v-show 是通过设置 DOM 元素的 display 样式属性控制显示隐藏,DOM 元素保留;
- 编译过程:v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show 只是简单的基于 CSS 切换;
- 编译条件:v-if 是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载); v-show 是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且 DOM 元素保留;
- 性能消耗:v-if 有更高的切换消耗;v-show 有更高的初始渲染消耗;
- 应用场景:v-if 适合运营条件不大可能改变;v-show 适合频繁切换。
Vue中v-if和v-show之间的区别
- 本质区别
v-show:把标签display设置为none,控制隐藏
v-if:动态的向DOM树内添加或者删除DOM元素
- 编译的区别
v-show:其实就是在控制css
v-if:切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件
监听和子组件
- 使用条件
只渲染一次使用v-if
反复渲染使用v-show
- 总结
v-if:不显示时,第一次就直接不渲染,如果内容以及显示就将其改为不显示,将内容直接从Dom去除,只是渲染一次用v-if
v-show:不显示时,就会改为display:none,但是会渲染在Dom上。所以反复切换内容的用v-show
Proxy
function observe(obj, callback) {
return new Proxy(obj, {
get(target, key) {
return target[key]
},
set(target, key, value) {
target[key] = value
callback(key, value)
}
})
}
const obj = observe(
{
name: '子君',
sex: '男'
},
(key, value) => {
console.log(`属性[${key}]的值被修改为[${value}]`)
}
)
// 这段代码执行后,输出 属性[name]的值被修改为[妹纸]
obj.name = '妹纸'
// 这段代码执行后,输出 属性[sex]的值被修改为[女]
obj.name = '女'
handle函数中的方法
handler
里面的方法可以有以下这十三个,每一个都对应的一种或多种针对proxy
代理对象的操作行为
-
handler.get
当通过
proxy
去读取对象里面的属性的时候,会进入到get
钩子函数里面 -
handler.set
当通过
proxy
去为对象设置修改属性的时候,会进入到set
钩子函数里面 -
handler.has
当使用
in
判断属性是否在proxy
代理对象里面时,会触发has
,比如const obj = { name: '子君' } console.log('name' in obj) 复制代码
-
handler.deleteProperty
当使用
delete
去删除对象里面的属性的时候,会进入deleteProperty`钩子函数 -
handler.apply
当
proxy
监听的是一个函数的时候,当调用这个函数时,会进入apply
钩子函数 -
handle.ownKeys
当通过
Object.getOwnPropertyNames
,Object.getownPropertySymbols
,Object.keys
,Reflect.ownKeys
去获取对象的信息的时候,就会进入ownKeys
这个钩子函数 -
handler.construct
当使用
new
操作符的时候,会进入construct
这个钩子函数 -
handler.defineProperty
当使用
Object.defineProperty
去修改属性修饰符的时候,会进入这个钩子函数 -
handler.getPrototypeOf
当读取对象的原型的时候,会进入这个钩子函数
-
handler.setPrototypeOf
当设置对象的原型的时候,会进入这个钩子函数
-
handler.isExtensible
当通过
Object.isExtensible
去判断对象是否可以添加新的属性的时候,进入这个钩子函数 -
handler.preventExtensions
当通过
Object.preventExtensions
去设置对象不可以修改新属性时候,进入这个钩子函数 -
handler.getOwnPropertyDescriptor
在获取代理对象某个属性的属性描述时触发该操作,比如在执行
Object.getOwnPropertyDescriptor(proxy, "foo")
时会进入这个钩子函数
Vue实现数据双向绑定
监听器
对data里面的数据对象做遍历(子对象存在时做递归遍历),利用Object.defineProperty
将data中的数据全部转换成getter/setter
,当某个属性和值发生改变时就能触发setter
,就会监听到数据的变化
解析器
模板解析器主要通过遍历模板,查看都使用了哪些变量、指令,为数据添加订阅者Watcher
,一旦数据发生了改变,调用更新函数更新。
订阅者Watcher
Watcher
订阅者是 Observer
和 Compile
之间通信的桥梁 ,主要的任务是订阅 Observer
中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile
中对应的更新函数。每个组件实例都有相应的 watcher
实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter
被调用时,会通知 watcher
重新计算,从而致使它关联的组件得以更新——这是一个典型的观察者模式
Vue生命周期
什么是生命周期?
每个Vue实例在被创建时都要经过一系列的初始化过程:
开始创建 -> 初始化数据 -> 编译模板 -> 挂载DOM-渲染 -> 更新-渲染 -> 销毁等一系列过程
通俗而言:Vue实例从创建到销毁的过程
生命周期分为8个过程:创建前,创建后,挂载前,挂载后,更新前,更新后,销毁前,销毁后。
vue生命周期的作用是什么?
在Vue实例经过一系列初始化的过程中也会运行一些 叫做 生命周期钩子 的函数, 生命週期裡邊这些事件鉤子,给予了用户在不同阶段可以添加自己代码的机会。
父子的生命周期函数执行顺序
- 进入页面自动执行生命周期钩子有:
首先会自动执行【父组件】的生命周期钩子beforeCreate -> created -> beforeMount
然后会自动执行【子组件】的生命周期钩子 beforeCreate -> created -> beforeMount-> mounted
最后在自动执行【父组件】的生命周期钩子mounted
- 当组件数据发生更改:【各自执行各自】的生命周期钩子
父组件自己的数据发生改变执行自己的生命周期钩子beforeUpdate、updated
子组件自己的数据发生改变执行自己的生命周期钩子beforeUpdate、updated
- 当组件被销毁时,执行生命周期钩子有:
父组件会先执行beforeDestroy
子组件在执行beforeDestroy、destroyed
最后父组件执行destroyed
Props
,methods
,data
和computed
的初始化都是在beforeCreated
和created
之间完成的。
Vue.set() & delete()
在vue的某一个状态添加新的属性或删除某一个属性的时候,vue并不能正确的检测到,并且无法响应式。
可以用Vue.set() 和 delete()
Vue.set(item,propertyName,value)
Vue.delete(item,propertyName)
这样vue才可以正常的响应式的做出反应
v-if 和 v-for
v-for优先级更高,这两个不应该同时使用 ,否则将会影响加载速度
比如 v-for=“item in arrlist” v-if=“item.flag”
arrlist中有100个数据,仅有一个数据的flag为true 那也会便利100次。应改为computed计算属性
Computed:{
items : {
return arrlist.fillter(item=>item.flag)
}
}
Vue-Router 路由守卫
有点类似生命周期函数
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。- 调用全局的
beforeEach
守卫。- 在重用的组件里调用
beforeRouteUpdate
守卫。- 在路由配置里调用
beforeEnter
。- 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。- 调用全局的
beforeResolve
守卫。- 导航被确认。
- 调用全局的
afterEach
钩子。- 触发
DOM
更新。- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
React
Redux
-
State 用来存放数据 和vuex相同
-
Action
是一个普通的javascript对象,用来描述发生什么了的指示器。
{ type:"ADD_TODO", text:"Go to Swimming Pool"}
-
reducer
执行器,类似vuex里的 mutation
reducer(state,action){ return newState}
hooks
- usestate 状态钩子
import React, {useState} from 'react'const AddCount = () => { const [ count, setCount ] = useState(0) const addcount = () => { let newCount = count setCount(newCount+=1) } return ( <> <p>{count}</p> <button onClick={addcount}>count++</button> </> )}export default AddCount
count为参数名称
setCount为赋值函数
useState(0) 0为默认值
- Use context 共享状态钩子
import React,{ useContext } from 'react'const Ceshi = () => { const AppContext = React.createContext({}) const A =() => { const { name } = useContext(AppContext) return ( <p>我是A组件的名字{name}<span>我是A的子组件{name}</span></p> )}const B =() => { const { name } = useContext(AppContext) return ( <p>我是B组件的名字{name}</p> )} return ( <AppContext.Provider value={{name: 'hook测试'}}> <A/> <B/> </AppContext.Provider> )}export default Ceshi
我们看下页面
可以看到,我们可以通过hooks做状态的共享。
- useReducer : Action 钩子
- useEffect 副作用钩子
import React, { useState, useEffect } from 'react'const AsyncPage = ({name}) => {const [loading, setLoading] = useState(true)const [person, setPerson] = useState({}) useEffect(() => { setLoading(true) setTimeout(()=> { setLoading(false) setPerson({name}) },2000) },[name]) return ( <> {loading?<p>Loading...</p>:<p>{person.name}</p>} </> )}const PersonPage = () =>{ const [state, setState] = useState('') const changeName = (name) => { setState(name) } return ( <> <AsyncPage name={state}/> <button onClick={() => {changeName('名字1')}}>名字1</button> <button onClick={() => {changeName('名字2')}}>名字2</button> </> )}export default PersonPage
类似componentdidmount ,useEffect(fun, state) state是依赖的属性,当state发生改变时会异步调用fun
- 自定义钩子
import React, { useState, useEffect } from 'react'const usePerson = (name) => {const [loading, setLoading] = useState(true)const [person, setPerson] = useState({}) useEffect(() => { setLoading(true) setTimeout(()=> { setLoading(false) setPerson({name}) },2000) },[name]) return [loading,person]}const AsyncPage = ({name}) => { const [loading, person] = usePerson(name) return ( <> {loading?<p>Loading...</p>:<p>{person.name}</p>} </> ) }const PersonPage = () =>{ const [state, setState]=useState('') const changeName = (name) => { setState(name) } return ( <> <AsyncPage name={state}/> <button onClick={() => {changeName('名字1')}}>名字1</button> <button onClick={() => {changeName('名字2')}}>名字2</button> </> )}export default PersonPage
usePerson 就是自定义的hooks
React Fiber
用来破解JavaScript同步操作时间过长,使别的任务长时间得不到运行。
解决办法,任务分片(碎片化)
把耗时较长的任务分片,每运行一个任务片之后 ,可以使其他任务运行
React Fiber把更新过程碎片化,执行过程如下面的图所示,每执行完一段更新过程,就把控制权交还给React负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务。
维护每一个分片的数据结构,就是Fiber。
在React Fiber中,一次更新过程会分成多个分片完成,所以完全有可能一个更新任务还没有完成,就被另一个更高优先级的更新过程打断,这时候,优先级高的更新任务会优先处理完,而低优先级更新任务所做的工作则会完全作废,然后等待机会重头再来。
因为一个更新过程可能被打断,所以React Fiber一个更新过程被分为两个阶段(Phase):第一个阶段Reconciliation Phase和第二阶段Commit Phase。
在第一阶段Reconciliation Phase,React Fiber会找出需要更新哪些DOM,这个阶段是可以被打断的;但是到了第二阶段Commit Phase,那就一鼓作气把DOM更新完,绝不会被打断。
这两个阶段大部分工作都是React Fiber做,和我们相关的也就是生命周期函数。
以render函数为界,第一阶段可能会调用下面这些生命周期函数,说是“可能会调用”是因为不同生命周期调用的函数不同。
- componentWillMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
下面这些生命周期函数则会在第二阶段调用。
componentDidMount
componentDidUpdate
componentWillUnmount
因为第一阶段的过程会被打断而且“重头再来”,就会造成意想不到的情况。
比如说,一个低优先级的任务A正在执行,已经调用了某个组件的componentWillUpdate函数,接下来发现自己的时间分片已经用完了,于是冒出水面,看看有没有紧急任务,哎呀,真的有一个紧急任务B,接下来React Fiber就会去执行这个紧急任务B,任务A虽然进行了一半,但是没办法,只能完全放弃,等到任务B全搞定之后,任务A重头来一遍,注意,是重头来一遍,不是从刚才中段的部分开始,也就是说,componentWillUpdate函数会被再调用一次。
在现有的React中,每个生命周期函数在一个加载或者更新过程中绝对只会被调用一次;在React Fiber中,不再是这样了,第一阶段中的生命周期函数在一次加载和更新过程中可能会被多次调用!
使用React Fiber之后,一定要检查一下第一阶段相关的这些生命周期函数,看看有没有逻辑是假设在一个更新过程中只调用一次,有的话就要改了。
我们挨个看一看这些可能被重复调用的函数。
componentWillReceiveProps,即使当前组件不更新,只要父组件更新也会引发这个函数被调用,所以多调用几次没啥,通过!
shouldComponentUpdate,这函数的作用就是返回一个true或者false,不应该有任何副作用,多调用几次也无妨,通过!
render,应该是纯函数,多调用几次无妨,通过!
只剩下componentWillMount和componentWillUpdate这两个函数往往包含副作用,所以当使用React Fiber的时候一定要重点看这两个函数的实现。
React 生命周期
挂载卸载过程
- constructor()
constructor()中完成了React数据的初始化,它接受两个参数:props和context,当想在函数内部使用这两个参数时,需使用super()传入这两个参数。
注意:只要使用了constructor()就必须写super(),否则会导致this指向错误。
- componentWillMount()
componentWillMount()一般用的比较少,它更多的是在服务端渲染时使用。它代表的过程是组件已经经历了constructor()初始化数据后,但是还未渲染DOM时。
- componentDidMount()
组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
- componentWillUnmount (),在此处完成组件的卸载和数据的销毁。
- clear你在组建中所有的setTimeout,setInterval
- 移除所有组建中的监听 removeEventListener
- 有时候我们会碰到这个warning:
Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the undefined component.
原因:因为你在组件中的ajax请求返回setState,而你组件销毁的时候,请求还未完成,因此会报warning
解决方法:
componentDidMount() { this.isMount === true axios.post().then((res) => { this.isMount && this.setState({ // 增加条件ismount为true时 aaa:res })})}componentWillUnmount() { this.isMount === false}
更新过程
- componentWillReceiveProps (nextProps)
在接受父组件改变后的props需要重新渲染组件时用到的比较多
接受一个参数nextProps
通过对比nextProps和this.props,将nextProps的state为当前组件的state,从而重新渲染组件
componentWillReceiveProps (nextProps) { nextProps.openNotice !== this.props.openNotice&&this.setState({ openNotice:nextProps.openNotice },() => { console.log(this.state.openNotice:nextProps) //将state更新为nextProps,在setState的第二个参数(回调)可以打 印出新的state })}
- shouldComponentUpdate(nextProps,nextState)
-
主要用于性能优化(部分更新)
-
唯一用于控制组件重新渲染的生命周期,由于在react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新,因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断
-
componentWillUpdate (nextProps,nextState),shouldComponentUpdate返回true以后,组件进入重新渲染的流程,进入componentWillUpdate,这里同样可以拿到nextProps和nextState。
-
componentDidUpdate(prevProps,prevState),组件更新完毕后,react只会在第一次初始化成功会进入componentDidmount,之后每次重新渲染后都会进入这个生命周期,这里可以拿到prevProps和prevState,即更新前的props和state。
-
render(),render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。
-
Constructor
ES5 子类继承父类时,是先创造子类的实例对象 然后使用 apply将父类的各个属性赋给子类
ES6 则为创造父类的实例对象,然后再用子类的构造函数修改属性
super(props)
可以不写constructor,一旦写了constructor,就必须在此函数中写super(),
此时组件才有自己的this,在组件的全局中都可以使用this关键字,
否则如果只是constructor 而不执行 super() 那么以后的this都是错的!!!
当想在constructor中使用this.props的时候,super需要加入(props),
此时用props也行,用this.props也行,他俩都是一个东西。(不过props可以是任意参数,this.props是固定写法)。
如果在custructor生命周期不使用 this.props或者props时候,可以不传入props。
如果constructor中不通过super来接收props,在其他生命周期,
诸如componentWillMount、componentDidMount、render中能直接使用this.props吗??
结论:可以的,react在除了constructor之外的生命周期已经传入了this.props了,完全不受super(props)的影响。
所以super中的props是否接收,只能影响constructor生命周期能否使用this.props,其他的生命周期已默认存在this.props
触发render方法的三种方式
在学习生命周期的,触发生命周期函数调用有三种方式
-
setState 改变状态的值
-
改变props的值
-
使用forceUpdate()方法
HOC 高阶组件
概括的讲,HOC能够实现:
-
代码复用,代码模块化
-
渲染劫持, 操作state
-
Props 增删改
-
基于属性代理(Props Proxy)的方式
属性代理是最常见的高阶组件的使用方式。可以通过对被包裹组件的props进行修改,或者添加新的props再传递给此组件,这就是属性代理。例如:
-
基于反向继承(Inheritance Inversion, 缩写II)的方式
反向继承的实现为:
返回的高阶组件类(Enhanced)继承了 WrappedComponent。这被叫做反向继承是因为 WrappedComponent 被动地被 Enhancer 继承,而不是 WrappedComponent 去继承 Enhancer。
反向继承允许高阶组件通过***this***关键词获取 WrappedComponent,意味着它可以获取到 state,props,组件生命周期 (component lifecycle)钩子,以及渲染方法(render)。
使用高阶组件遇到的问题
-
静态方法丢失
当使用高阶组件包裹原始组件,返回的新组件会丢失原始组件的所有静态方法。例如:
解决方案:
- 手动拷贝所有静态方法给新组件;(不推荐)
- 使用hoist-non-react-statics来帮你自动处理,它会自动拷贝所有非React的静态方法;(react-router 里withRouter就使用了这个包)
-
Refs属性不能传递
解决方案:
新组建传递一个ref 回调函数属性给原始组件,也就是将ref组件绑定到新组件上;如:
<Enhancer getRef={ref => this.wrappedC = ref} />
React 事件合成系统
完全符合w3c标准,没有兼容问题
React自己实现了一套高效的事件注册,存储,分发和重用逻辑,在DOM事件体系基础上做了很大改进,减少了内存消耗,简化了事件逻辑,并最大化的解决了IE等浏览器的不兼容问题。与DOM事件体系相比,它有如下特点
- React组件上声明的事件最终绑定到了document这个DOM节点上,而不是React组件对应的DOM节点。故只有document这个节点上面才绑定了DOM原生事件,其他节点没有绑定事件。这样简化了DOM原生事件,减少了内存开销
- React以队列的方式,从触发事件的组件向父组件回溯,调用它们在JSX中声明的callback。也就是React自身实现了一套事件冒泡机制。我们没办法用event.stopPropagation()来停止事件传播,应该使用event.preventDefault()
- React有一套自己的合成事件SyntheticEvent,不同类型的事件会构造不同的SyntheticEvent
- React使用对象池来管理合成事件对象的创建和销毁,这样减少了垃圾的生成和新对象内存的分配,大大提高了性能
##########################
前端模块化 ES6、CommonJS、AMD、CMD
https://juejin.cn/post/6844903744518389768
一、CommonJS
Node.js是commonJS规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:module
、exports
、require
、global
。实际使用时,用module.exports
定义当前模块对外输出的接口(不推荐直接用exports
),用require
加载模块。
// 定义模块math.jsvar basicNum = 0;function add(a, b) { return a + b;}module.exports = { //在这里写上需要向外暴露的函数、变量 add: add, basicNum: basicNum}// 引用自定义的模块时,参数包含路径,可省略.jsvar math = require('./math');math.add(2, 5);// 引用核心模块时,不需要带路径var http = require('http');http.createService(...).listen(3000);复制代码
commonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
二、AMD和require.js
AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。这里介绍用require.js实现AMD规范的模块化:用require.config()
指定引用路径等,用define()
定义模块,用require()
加载模块。
首先我们需要引入require.js文件和一个入口文件main.js。main.js中配置require.config()
并规定项目中用到的基础模块。
/** 网页中引入require.js及main.js **/<script src="js/require.js" data-main="js/main"></script>/** main.js 入口文件/主模块 **/// 首先用config()指定各模块路径和引用名require.config({ baseUrl: "js/lib", paths: { "jquery": "jquery.min", //实际路径为js/lib/jquery.min.js "underscore": "underscore.min", }});// 执行基本操作require(["jquery","underscore"],function($,_){ // some code here});复制代码
引用模块的时候,我们将模块名放在[]
中作为reqiure()
的第一参数;如果我们定义的模块本身也依赖其他模块,那就需要将它们放在[]
中作为define()
的第一参数。
// 定义math.js模块define(function () { var basicNum = 0; var add = function (x, y) { return x + y; }; return { add: add, basicNum :basicNum };});// 定义一个依赖underscore.js的模块define(['underscore'],function(_){ var classify = function(list){ _.countBy(list,function(num){ return num > 30 ? 'old' : 'young'; }) }; return { classify :classify };})// 引用模块,将模块放在[]内require(['jquery', 'math'],function($, math){ var sum = math.add(10,20); $("#sum").html(sum);});复制代码
三、CMD和sea.js
require.js在申明依赖的模块时会在第一之间加载并执行模块内的代码:
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { // 等于在最前面声明并初始化了要用到的所有模块 if (false) { // 即便没用到某个模块 b,但 b 还是提前执行了 b.foo() } });复制代码
CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。
/** AMD写法 **/define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { // 等于在最前面声明并初始化了要用到的所有模块 a.doSomething(); if (false) { // 即便没用到某个模块 b,但 b 还是提前执行了 b.doSomething() } });/** CMD写法 **/define(function(require, exports, module) { var a = require('./a'); //在需要时申明 a.doSomething(); if (false) { var b = require('./b'); b.doSomething(); }});/** sea.js **/// 定义模块 math.jsdefine(function(require, exports, module) { var $ = require('jquery.js'); var add = function(a,b){ return a+b; } exports.add = add;});// 加载模块seajs.use(['math.js'], function(math){ var sum = math.add(1+2);});复制代码
四、ES6 Module
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export
和import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
/** 定义模块 math.js **/var basicNum = 0;var add = function (a, b) { return a + b;};export { basicNum, add };/** 引用模块 **/import { basicNum, add } from './math';function test(ele) { ele.textContent = add(99 + basicNum);}复制代码
如上例所示,使用import
命令的时候,用户需要知道所要加载的变量名或函数名。其实ES6还提供了export default
命令,为模块指定默认输出,对应的import
语句不需要使用大括号。这也更趋近于ADM的引用写法。
/** export default **///定义输出export default { basicNum, add };//引入import math from './math';function test(ele) { ele.textContent = math.add(99 + math.basicNum);}复制代码
ES6的模块不是对象,import
命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。
五、 ES6 模块与 CommonJS 模块的差异
1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
- ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令
import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import
有点像 Unix 系统的“符号连接”,原始值变了,import
加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- 运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
- 编译时加载: ES6 模块不是对象,而是通过
export
命令显式指定输出的代码,import
时采用静态命令的形式。即在import
时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。
CommonJS 加载的是一个对象(即module.exports
属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
原型链
function Person() {}var person = new Person();person.name = 'Kevin';console.log(person.name) // Kevin
Person为构造函数
person为实例对象
构造函数指向原型
Person.prototype
person和Person都是通过Person的原型 prototype构造出来的
所以person.prototype === Person.prototype
constructor是构造函数的意思
Person.prototype.constructor === Person
实例对象指向原型
person.proto === Person.prototype
数组API
ES5
名字 | 描述 | 是否改变原对象 | 参数 |
---|---|---|---|
arr.join(",") | 数组变字符串 | 否 | 用,连接 |
str.split(",") | 字符串变数组 | 否 | 用,分隔 |
arr.push(‘a’) | 像数组添加字符串 | 是 | 添加a,可以多个参数用,分割 |
b=arr.pop() | 弹出组数尾元素 | 是 | 弹出尾元素并赋值给b |
b=arr.shift() | 弹出头元素 | 是 | 弹出头元素赋值给b |
arr.unshift(a) | 向数组头添加a | 是 | 字符串添加到数组头 |
arr.reverse() | 数组倒序 | 是 | |
arr.sort() | 数组排序 | 是 | |
a = arrb.concat© | 连接数组 | 不改变 | 把b和c数组连接起来赋值给a,c可为字符串 |
a=arrb.slice(i,j) | 分割数组 | 不改变 | 从[i,j)分割b赋值给a |
a=arrb.splice(i,j) | 分割数组 | 改变 | 从[i,j)分割b赋值给a |
arr.indexOf(a) | 查找索引 | 不改变 | 查找a在数组中的位置,返回索引,不存在返回-1 |
arr.lastIndexOf(a) | 查找索引 | 不改变 | 查找a在数组中最后的位置,返回索引,不存在返回-1 |
防抖和节流
-
防抖
在于清零计时器
function debounce (f, wait) { let timer return (...args) => { clearTimeout(timer) timer = setTimeout(() => { f(...args) }, wait) }}
f为执行函数,wait为计时时间ms
-
节流
在于锁
function throttle (f, wait) { let timer return (...args) => { if (timer) { return } timer = setTimeout(() => { f(...args) timer = null }, wait) }}
隐藏一个元素的五种方法
-
display:none
-
visibility:hidden
-
opacity:0 不透明度
-
绝对定位 position:absolute left和top给两个大负值
-
相对定位 position:relative left和top给两个大负值
箭头函数指向
箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
var x=11;var obj={ x:22, say:()=>{ console.log(this.x); }}obj.say();//11var a=11;function test2(){ this.a=22; let b=()=>{console.log(this.a)} b();}var x=new test2();//22
前端路由的实现
hash模式
这里的 hash 就是指 url 后的 # 号以及后面的字符。比如说 “www.baidu.com/#hashhash” ,其中 “#hashhash” 就是我们期望的 hash 值。
由于 hash 值的变化不会导致浏览器像服务器发送请求,而且 hash 的改变会触发 hashchange 事件,浏览器的前进后退也能对其进行控制,所以在 H5 的 history 模式出现之前,基本都是使用 hash 模式来实现前端路由。
window.location.hash = 'hash字符串'; // 用于设置 hash 值let hash = window.location.hash; // 获取当前 hash 值// 监听hash变化,点击浏览器的前进后退会触发window.addEventListener('hashchange', function(event){ let newURL = event.newURL; // hash 改变后的新 url let oldURL = event.oldURL; // hash 改变前的旧 url},false)
history模式
//H5之前history.go(-1); // 后退一页history.go(2); // 前进两页history.forward(); // 前进一页history.back(); // 后退一页//H5之后history.pushState(); // 添加新的状态到历史状态栈history.replaceState(); // 用新的状态代替当前状态history.state // 返回当前状态对象
如何删除cookies
max-age:如果设置为负值的话,则为浏览器进程Cookie(内存中保存),关闭浏览器就失效;如果设置为0,则立即删除该Cookie。
**expiress:**设置为当前时间之前的时间,即可立即删除该cookie
Promise
Promise
是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理和更强大。
有了Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
一个Promise
的当前状态必须为以下三种状态中的一种:等待态(Pending
)、执行态(Fulfilled
)和拒绝态(Rejected
),状态的改变只能是单向的,且变化后不可在改变。
一个Promise
必须提供一个 then
方法以访问其当前值、终值和据因。
promise.then(onFulfilled, onRejected)
回调函数只能执行一次,且返回 promise
对象
Promise
的每个操作返回的都是Promise
对象,可支持链式调用。
通过 then
方法执行回调函数,Promise
的回调函数是放在事件循环中的微队列。
function fn(){ return new Promise((resolve, reject)=>{ 成功时调用 resolve(数据) 失败时调用 reject(错误) }) } fn().then(success1, fail1).then(success2, fail2)
Promise.all()
Promise.all 接收一个 promise 对象的数组作为参数,当这个数组里的所有 promise 对象全部变为resolve或 有 reject 状态出现的时候,它才会去调用 .then 方法,它们是并发执行的
-
Promise.all()接受一个 Array 类型的参数
-
只有数组中全部的 Promise 都变为 resolve 的时候
-
返回一个新的 Promise 实例
-
只要有一个失败,状态就变成 rejected
function PromiseAll(arr) { //PromiseAll的返回值为一个promise对象 return new Promise((resolve, reject) => { //PromiseAll的入参必须是数组 if (!Array.isArray(arr)) { return reject(new TypeError('arr must be an array.')); } let resArr = []; for (let i in arr) { (function(i) { Promise.resolve(arr[i]).then(res => { resArr.push(res); //只有所有的都成功了,才会返回resolve if (i == arr.length - 1) { return resolve(resArr); } }, err => { // 只要出错就抛出 return reject(err) }).catch(err => { console.log(err) }) })(i) } })}//测试const pro1 = new Promise((res,rej) => { setTimeout(() => { res('1') },1000)})const pro2 = new Promise((res,rej) => { setTimeout(() => { res('2') },2000)})const pro3 = new Promise((res,rej) => { setTimeout(() => { res('3') },3000)})const proAll = PromiseAll([pro1,pro2,pro3]).then(res => console.log(res) // 3秒之后打印 ["1", "2", "3"]).catch((e) => { console.log(e)})
精简
all
Promise.all()
方法用于将多个 Promise
实例,包装成一个新的 Promise
实例。
Promise.all([promise1, promise2]).then(success1, fail1)promise1`和`promise2`都成功才会调用`success1
any
任意一个 成功就会成功,失败一个不会变化,直到全部失败才会失败
race
Promise.race()方法同样是将多个
Promise实例,包装成一个新的
Promise` 实例。
赛跑,哪个promise 先进行状态改变
Promise.race([promise1, promise2]).then(success1, fail1)promise1`和`promise2`只要有一个成功就会调用`success1
allsetled
所有的promise全部改变完状态之后(不论成功或失败),才会成功,不会失败。
new的实现原理
- 创建一个空对象,构造函数中的this指向这个空对象
- 这个新对象被执行 [[原型]] 连接
- 执行构造函数方法,属性和方法被添加到this引用的对象中
- 如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
XSS CSRF 攻击
-
XSS
-
存储型
顾名思义就是将恶意脚本存储了起来,确实,存储型的 XSS 将脚本存储到了服务端的数据库,然后在客户端执行这些脚本,从而达到攻击的效果。
常见的场景是留言评论区提交一段脚本代码,如果前后端没有做好转义的工作,那评论内容存到了数据库,在页面渲染过程中直接执行, 相当于执行一段未知逻辑的 JS 代码,是非常恐怖的。这就是存储型的 XSS 攻击。
-
文档型
文档型的 XSS 攻击并不会经过服务端,而是作为中间人的角色,在数据传输过程劫持到网络数据包,然后修改里面的 html 文档!
这样的劫持方式包括
WIFI路由器劫持
或者本地恶意软件
等。 -
反射型
反射型XSS指的是恶意脚本作为网络请求的一部分。
比如我输入:
http://sanyuan.com?q=<script>alert("你完蛋了")</script>
在服务器端会拿到q参数,然后将内容返回给浏览器端,浏览器将这些内容作为HTML的一部分解析,发现是一个脚本,直接执行,这样就被攻击了。之所以叫它反射型, 是因为恶意脚本是通过作为网络请求的参数,经过服务器,然后再反射到HTML文档中,执行解析。和存储型不一样的是,服务器并不会存储这些恶意脚本。
-
防范
-
-
CSRF
- 跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为CSRF 或者 XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。如:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
- 防范
- 验证码:强制用户必须与应用进行交互,才能完成最终请求。此种方式能很好的遏制 csrf,但是用户体验比较差。
- Referer check:请求来源限制,此种方法成本最低,但是并不能保证 100% 有效,因为服务器并不是什么时候都能取到 Referer,而且低版本的浏览器存在伪造 Referer 的风险。
- token:token 验证的 CSRF 防御机制是公认最合适的方案。但若网站同时存在 XSS 漏洞的时候,这个方法也是空谈
BFC
BFC
全称:Block Formatting Context
, 名为 “块级格式化上下文”
如何创建
-
根元素
-
浮动元素(float 属性不为 none)
-
position 为 absolute 或 fixed
-
overflow 不为 visible 的块元素
-
display 为 inline-block, table-cell, table-caption
应用
-
防止 margin 重叠 (同一个BFC内的两个两个相邻Box的
margin
会发生重叠,触发生成两个BFC,即不会重叠) -
清除内部浮动 (创建一个新的 BFC,因为根据 BFC 的规则,计算 BFC 的高度时,浮动元素也参与计算)
-
自适应多栏布局 (BFC的区域不会与float box重叠。因此,可以触发生成一个新的BFC)
BFC布局规则
BFC内,盒子依次垂直排列。
BFC内,两个盒子的垂直距离由 margin
属性决定。属于同一个BFC的两个相邻Box的margin会发生重叠【符合合并原则的margin合并后是使用大的margin】
BFC内,每个盒子的左外边缘接触内部盒子的左边缘(对于从右到左的格式,右边缘接触)。即使在存在浮动的情况下也是如此。除非创建新的BFC。
BFC的区域不会与float box重叠。
BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
计算BFC的高度时,浮动元素也参与计算。
transfrom的值,会引起重排和重绘吗
不会,因为 GPU 进程会为其开启一个新的复合图层,不会影响默认复合图层(就是普通文档流),所以并不会影响周边的 DOM 结构,而属性的改变也会交给 GPU 处理,不会进行重排。
使 GPU 进程开启一个新的复合图层的方式还有 3D 动画,过渡动画,以及 opacity 属性,还有一些标签,这些都可以创建新的复合图层。这些方式叫做硬件加速方式。你可以想象成新的复合图层和默认复合图层是两幅画,相互独立,不会彼此影响。降低重排的方式:要么减少次数,要么降低影响范围,创建新的复合图层就是第二种优化方式。绝对布局虽然脱离了文档流,但不会创建新的复合图层,因此当绝对布局改变时,不会影响普通文档流的 render tree,但是依然会绘制整个默认复合图层,对普通文档流是有影响的。普通文档流就是默认复合图层,不要介意我交换使用它们如果你要使用硬件加速方式降低重排的影响,请不要过度使用,创建新的复合图层是有额外消耗的,比如更多的内存消耗,并且在使用硬件加速方式时,配合 z-index 一起使用,尽可能使新的复合图层的元素层级等级最高。
for of 和 for in
for…of 用于遍历一个迭代器,如数组:
let letter = ['a', 'b', 'c'];letter.size = 3;for (let letter of letters) { console.log(letter);}// 结果: a, b, c复制代码
for…in 用来遍历对象中的属性:
let stu = ['Sam', '22', '男'];stu.size = 3;for (let stu in stus) { console.log(stu);}// 结果: Sam, 22, 男
CSS
CSS画三角梯形扇形箭头和椭圆
https://juejin.cn/post/6844904062593269768
padding-top :50%
50%是基于父元素宽度的百分比
IE盒模型 、 标准盒模型
默认为标准盒模型
标准 :盒子的宽高取决于content
IE怪异 : 盒子的宽高取决于padding + content + border
box-sizing : content-box //默认标准盒模型 border-box //怪异盒模型 inherit//继承父类属性
:before
伪类 ,不设置伪类width时,宽度100%。背景颜色全占满。设置宽度为50px时,背景颜色只有50px,但是伪类仍然占据100%宽度
样式导入
-
行内样式(优先级最高)
<div style="color:red;font-size:18px"></div>
-
内联样式表
<style type="text/css"> .box{ } </style>
-
外部样式表(优先级最低)
- 链接式
<link rel="stylesheet" type="text/css" href="./style.css" />
- 导入式
<head> <style> @import url("./style.css"); </style> </head>
两种方式都必须写在head里
链接形式是先加载html 再加载css
导入形式是先加载css 再加载html
VW VH EM REM
长度单位,1vw 等于视口宽度的1%,1vh 等于视口高度的1%
vmax :视口宽度和高度最大的那个
vmin :视口宽度和高度最小的那个
em和rem与字体有关
em : 1em 等于 100%当前盒子字体大小,当前盒子没设置字体大小就继承父类
rem : 1rem 等于 100%根元素字体大小
字体最小12px ,没有最大,默认16px
垂直居中
父类高度未知 子绝父相
父类高度已知 直接子相
transform
在CSS3中transform主要包括以下几种:旋转rotate、扭曲skew、缩放scale和移动translate以及矩阵变形matrix。
- 旋转:rotate() 顺时针旋转给定的角度,允许负值 rotate(30deg)
- 扭曲:skew() 元素翻转给定的角度,根据给定的水平线(X 轴)和垂直线(Y 轴)参数:skew(30deg,20deg)
- 缩放:scale() 放大或缩小,根据给定的宽度(X 轴)和高度(Y 轴)参数: scale(2,4)
- 移动:translate() 平移,传进 x,y值,代表沿x轴和y轴平移的距离,%为自身长度
matrix(n,n,n,n,n,n) | 定义 2D 转换,使用六个值的矩阵。 |
---|---|
translate(x,y) | 定义 2D 转换,沿着 X 和 Y 轴移动元素。 |
translateX(n) | 定义 2D 转换,沿着 X 轴移动元素。 |
translateY(n) | 定义 2D 转换,沿着 Y 轴移动元素。 |
scale(x,y) | 定义 2D 缩放转换,改变元素的宽度和高度。 |
scaleX(n) | 定义 2D 缩放转换,改变元素的宽度。 |
scaleY(n) | 定义 2D 缩放转换,改变元素的高度。 |
rotate(angle) | 定义 2D 旋转,在参数中规定角度。 |
skew(x-angle,y-angle) | 定义 2D 倾斜转换,沿着 X 和 Y 轴。 |
skewX(angle) | 定义 2D 倾斜转换,沿着 X 轴。 |
skewY(angle) | 定义 2D 倾斜转换,沿着 Y 轴。 |
transition
Transition作用是指定了某一个属性(如width、left、transform等)在两个值之间如何过渡。
如果某一个元素指定了Transiton,那么当其某个属性改变的时候就会按照Transition指定的方式进行过渡,动画就这么产生了。
transition主要包含四个属性值:all 0 ease 0
-
执行变换的属性:transition-property; 默认 all
-
变换延续的时间:transition-duration; 默认 0s
-
在延续时间段,变换的速率变化:transition-timing-function //例:平缓进入、先快后慢; 默认 ease
-
linear 规定以相同速度开始至结束的过渡效果(等于 cubic-bezier(0,0,1,1))。 ease 规定慢速开始,然后变快,然后慢速结束的过渡效果(cubic-bezier(0.25,0.1,0.25,1))。 ease-in 规定以慢速开始的过渡效果(等于 cubic-bezier(0.42,0,1,1))。 ease-out 规定以慢速结束的过渡效果(等于 cubic-bezier(0,0,0.58,1))。 ease-in-out 规定以慢速开始和结束的过渡效果(等于 cubic-bezier(0.42,0,0.58,1))。 cubic-bezier(n,n,n,n) 在 cubic-bezier 函数中定义自己的值。可能的值是 0 至 1 之间的数值。
-
-
变换延迟时间:transition-delay。 默认 0s
需要事件的触发,例如单击、获取焦点、失去焦点等。
语法:transition:property duration timing-function delay;
例如:transition:width 2s ease 0s;
transition : all .5s;
所有效果0.5秒完成
Animation
Animation也是通过指定某一个属性(如width、left、transform等)在两个值之间如何过渡来实现动画的,与Transition不同的是:
Animation可以通过keyframe显式控制当前帧的属性值,而Transition只能隐式来进行(不能指定每帧的属性值),所以相对而言Animation的功能更加灵活;
Animation通过模拟属性值改变来实现动画,动画结束之后元素的属性没有变化;而Transition确实改变了元素的属性值,动画结束之后元素的属性发生了变化;这一点,这在实际应用中会产生很大的区别。
Animation模块包括了animation-name、animation-duration、animation-timing-function、animation-delay、animation-iteration-count、animation-play-state等属性。
事件委托
事件委托利用事件冒泡机制指定一个事件处理程序,用来管理某一类型的所有事件。在操作dom动态添加元素而该元素正好也要绑定事件,那么把该事件绑定在其父元素上通过target获取真实目标,就可以给该元素也绑定上事件,这样做的好处是只在内存中开辟了一块空间,节省资源同时减少了dom操作。
let myUl = document.getElementById("my-ul"); myUl.addEventListener('click',function(e){ if(e.target.tagName == "LI"){ //如果点击的目标的标签名为LI alert(e.target.innerText) } })
object.defineProperty
class Vue { constructor(options) { this._data = options.data; this.observer(); } observer() { let keys = Object.keys(this._data); console.log(keys) keys.forEach((key) => { let val = this._data[key]; Object.defineProperty(this._data, key, { configurable: true, get() { return val; }, set(newValue) { if (newValue !== val) { val = newValue; console.log(`data中的${key}被修改为了${newValue}`) } } }) }) }}
本地储存
LocalStorage
-
存储量
一般5M左右,不同浏览器会有一点差别 -
安全性
不会放进请求头,可以节省大量的带宽,缩短请求时间,更加安全 -
生命周期
永久,需用户手动清除
语法 (API)
localStorage.setItem(k,v) //设置localStorage.getItem(k) // 获取localStorage.removeItem(k) //删除localStorage.clear() //清空所有localStorage.k=v // 修改数据localStorage.length // 获取所有键值对的数量localStorage.key(n) // 获取第n个键值对
storage 事件
当同源的localStorage有所改变时,会触发这个事件,必须在同一个服务器下运行
事件对象event中存了一些东西:
event.key //字符串,被修改元素的键event.oldValue //任意类型,被修改数据修改前的值,如果是增加数据,那么这个值是nullevent.newValue //任意类型,数据修改后的值event.url //修改数据的网页的url
cookie
后台设置cookie:
setCookie(name,value,expire,path)//参数1:cookie名称//参数2:cookie内容//参数3:过期时间//参数4:cookie在服务器的有效地址
后台读取cookie:
$_COOKIE //前端获取cookie: document.cookie //cookie是前端浏览器的一个存储机制
安全性
在向后台发送http请求时,cookie的内容会包裹在请求头中发送给后台
存储量
大小限制在4k
具有保质期
设置短期时间或长期时间
sessionStorage
生命周期
会话窗口内,一旦关闭会话窗口,存储消失,数据清空
语法 (API)
sessionStorage.setItem(k,v) //设置sessionStorage.getItem(k) // 获取sessionStorage.removeItem(k) //删除sessionStorage.clear() //清空所有sessionStorage.k=v // 修改数据sessionStorage.length // 获取所有键值对的数量sessionStorage.key(n) // 获取第n个键值对
安全性
不会放进请求头,可以节省大量的带宽,缩短请求时间,更加安全
session
session是后台服务器的存储机制
session会话原理:
每一个服务器访问者都有一个唯一的id(UID),并且基于这个UID来存储变量,UID存储变在cookie中,或者可以通过url进行传输
session用法
//1) 开启会话:session_start();//2) 设置和读取session$_SESSION['name']='lisi';$name=$_SESSION['name'];//3) 获取sessionIDsession_id();//4) 释放单个sessionunset($_SESSION['age']);//5) 释放多个session $_SESSION=array();//6)彻底释放当前sessionsession_unset();//释放session的标准方法,释放完还可以继续添加内容//7)彻底销毁session//先销毁内容:session_unset();//再销毁session以及sessionID:session_destory();//如果想再次在session中存取内容,需要重新开启session,session_start()
session与cookie的区别与利用
session是存放在服务器的,但是cookie是存放在浏览器的
cookie不安全,别人可以分析存储在本地的cookie而读取你的隐私,考虑安全方面,我们选择session
session会在一定时间你保存在服务器,当访问量增多的时候回占用大量的服务器内存,影响服务器性能,考虑减轻服务器负担,我们选择cookie
cookie存储量不超过4k,很多浏览器限制保存20个cookie,考虑个数,我们选择session
localStorage、cookie、sessionStorage的区别
cookie:存储量小,存储麻烦,没有固定的API,需要后台帮忙设置,操作麻烦,不安全,费流量
localStorage:没有时间限制,永不过期,除非主动删除,数据可跨越窗口使用,无视当前会话
sessionStorage:在任何页面都可以存储信息,但是仅仅在当前页面有用,每个页面存储的信息都是独立拥有的,其他页面不能访问,一旦当前页面关闭,存储的信息全部删除
进程间通信
管道通信
:就是操作系统在内核中开辟一段缓冲区,进程1可以将需要交互的数据拷贝到这个缓冲区里,进程2就可以读取了
消息队列通信
:消息队列就是用户可以添加和读取消息的列表,消息队列里提供了一种从一个进程向另一个进程发送数据块的方法,不过和管道通信一样每个数据块有最大长度限制
共享内存通信
:就是映射一段能被其他进程访问的内存,由一个进程创建,但多个进程都可以访问,共享进程最快的是IPC
方式
信号量通信
:比如信号量初始值是1,进程1来访问一块内存的时候,就把信号量设为0,然后进程2也来访问的时候看到信号量为0,就知道有其他进程在访问了,就不访问了
socket
:其他的都是同一台主机之间的进程通信,而在不同主机的进程通信就要用到socket的通信方式了,比如发起http请求,服务器返回数据
实现一个事件类
class EventEmitter{ constructor(){ this._events = {} } on(event,callback){ //监听event事件,触发时调用callback函数 let callbacks = this._events[event] || [] callbacks.push(callback) this._events[event] = callbacks return this } off(event,callback){ //停止监听event事件 let callbacks = this._events[event] this._events[event] = callbacks && callbacks.filter(fn => fn !== callback) return this } emit(...args){ //触发事件,并把参数传给事件的处理函数 const event = args[0] const params = [].slice.call(args,1) const callbacks = this._events[event] callbacks.forEach(fn => fn.apply(this.params)) return this } once(event,callback){ //为事件注册单次监听器 let wrapFanc = (...args) => { callback.apply(this.args) this.off(event,wrapFanc) } this.on(event,wrapFanc) return this } }
JS最小精确度
console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);
知道哪些content-type,通常ajax用哪种
1,application/x-www-form-urlencoded
浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。
2,multipart/form-data
我们使用表单上传文件时,就要让 form 的 enctype 等于这个值。
3,application/json
application/json 这个 Content-Type 作为响应头大家肯定不陌生。实际上,现在越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 JSON 字符串。由于 JSON 规范的流行,除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify,服务端语言也都有处理 JSON 的函数,使用 JSON 不会遇上什么麻烦。
4,text/xml
相比于JSON,XML不能更好的适用于数据交换,它包含了太多的包装, 而且它跟大多数编程语言的数据模型不匹配,让大多数程序员感到诧异,XML是面向数据的,JSON是面向对象和结构的,后者会给程序员一种更加亲切的感觉。
我们现在一般这样来使用:
1、XML 存储数据,存储配置文件等需要结构化存储的地方使用;
2、数据传输、数据交互使用JSON;
手撕常用API
Map
Array.prototype.myMap = function(fn){ let arr = this let res = [] arr.forEach(x=>{ res.push(fn(x)) }) return res}let arr = [1,2,3,4,5]console.log(arr.myMap(x=>x+1))
Filter
Array.prototype.myFilter = function(fn){ let arr = this let res = [] arr.forEach(x=>{ if(fn(x)) res.push(x) }) return res}let arr = [1,-2,3,-4,5]console.log(arr.myFilter(x=>x>0))
Reduce
Array.prototype.myReduce = function(fn,fir){ let array = this let flag = fir ? true : false let firValue = flag ? fir : array[0] let pre = firValue for(let i = flag ? 0 : 1;i<array.length;i++){ pre = fn(pre,array[i]) } return pre}let arr = [1,-2,3,-4,5]console.log(arr.myReduce((a,b)=>a+b,3))
Flatten
//递归 let arr = [1, [2, [3, 4]]]; function flattern(arr,result =[]) { for(let i = 0; i < arr.length; i++) { if(Array.isArray(arr[i])) { flattern(arr[i], result) } else { result.push(arr[i]) } } return result; }console.log(flattern(arr));//reducelet arr = [1, [2, [3, 4]]];function flatten(arr) { return arr.reduce(function(prev, next){ return prev.concat(Array.isArray(next) ? flatten(next) : next) }, [])}console.log(flatten(arr))//ES6扩展运算符const arr = [1,2,[3,4,5,[6,7],8],9,10,[11,[12,13]]];const flatten = (arr) => { while(arr.some(item=>Array.isArray(item))){ arr=[].concat(...arr); } return arr;}console.log(flatten(arr)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]//reduce搭配扩展运算符const flatten = (array) => array.reduce((acc,cur)=> (Array.isArray(cur)?[...acc,...flatten(cur)]:[...acc,cur]),[])
instanceof
function fun(a,b){ if(a.__proto__ == null) return false if(b.prototype == null) return false let ap = a.__proto__ if(ap == b.prototype) return true else{ return fun(ap,b) }}let arr = {}console.log(fun(arr,Array))
JS原型
首先,要明确几个点:
1.在JS里,万物皆对象。方法(Function)是对象,方法的原型(Function.prototype)是对象。因此,它们都会具有对象共有的特点。
即:对象具有属性__proto__,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。
2.方法(Function)
方法这个特殊的对象,除了和其他对象一样有上述_proto_属性之外,还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。
好啦,知道了这两个基本点,我们来看看上面这副图。
1.构造函数Foo()
构造函数的原型属性Foo.prototype指向了原型对象,在原型对象里有共有的方法,所有构造函数声明的实例(这里是f1,f2)都可以共享这个方法。
2.原型对象Foo.prototype
Foo.prototype保存着实例共享的方法,有一个指针constructor指回构造函数。
3.实例
f1和f2是Foo这个对象的两个实例,这两个对象也有属性__proto__,指向构造函数的原型对象,这样子就可以像上面1所说的访问原型对象的所有方法啦。
另外:
构造函数Foo()除了是方法,也是对象啊,它也有__proto__属性,指向谁呢?
指向它的构造函数的原型对象呗。函数的构造函数不就是Function嘛,因此这里的__proto__指向了Function.prototype。
其实除了Foo(),Function(), Object()也是一样的道理。
原型对象也是对象啊,它的__proto__属性,又指向谁呢?
同理,指向它的构造函数的原型对象呗。这里是Object.prototype.
最后,Object.prototype的__proto__属性指向null。
总结:
1.对象有属性__proto__,指向该对象的构造函数的原型对象。
2.方法除了有属性__proto__,还有属性prototype,prototype指向该方法的原型对象。
src 和 href 的区别
src 代表引用资源,请求到会代替这个位置,一般用在img script iframe标签
href 是超文本引用,一般用在 link 和 a标签
link 和 @import 区别
区别1:link是XHTML标签,除了加载CSS外,还可以定义RSS等其他事务;@import属于CSS范畴,只能加载CSS。
区别2:link引用CSS时,在页面载入时同时加载;@import需要页面网页完全载入以后加载。
区别3:link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。
区别4:ink支持使用Javascript控制DOM去改变样式;而@import不支持。
for in / for of
-
for in 遍历对象得到键、遍历数组得到索引
let arr = [10,20,30]for(i in arr){ console.log(i) //0,1,2, console.log(arr[i]) //10,20,30}let obj = { name : "zlx", id : 10, age : 21}for(i in obj){ console.log(i) // name,id,age console.log(obj[i]) // zlx , 10 ,21}
-
for of 遍历数组直接得到值,不能遍历对象
let arr = [10,20,30]for(i of arr){ console.log(i) //10,20,30}
JS 静态属性
Static 修饰
静态方法 是类自身的属性,不会被实例继承。如果实例调用会报不存在该方法
但是 子类可以继承 静态方法
深拷贝
let old = {
name : 'zlx',
say : function(){
console.log(this.name)
},
data : {
qwe : 'qwe'
}
}
let newd = deepclone(old)
newd.name = 'ljx'
newd.data.qwe = 'zxc'
console.log(old,newd)
function deepclone(old){
if(typeof old !== 'object') return old
console.log(Object.prototype.toString.call(old))
let newd
if(Object.prototype.toString.call(old) === '[object Array]' ){
console.log(1)
newd = []
}else if(Object.prototype.toString.call(old) === '[object Object]' ){
console.log(2)
newd = {}
}
for(i in old){
newd[i] = deepclone(old[i])
}
return newd
}
虚拟Dom
- 虚拟dom是建立在原生dom上的抽象,每次更新需要遍历数组中的节点,并与之前的节点进行比较, 将比较之后的新的dom数组一次性提交到原生dom树上进行渲染, 这就是diff最简单的理解.
- 不过当节点数量非常庞大时,需要的计算和判断会拖垮它引以为傲的性能. 单纯从创建dom来说, 它的创建速度比原生的慢. 毕竟是个中间商.相当于它有很多自己的服务.
- 整体来说, 虚拟dom是现代前端必需的东西. 它减少了我们频繁的操作原生dom,并且避免程序员操作dom带来的差异性,这种差异性也是影响到了性能很重要的一个因素
进程间通信
-
管道通信
管道(pipe)分为匿名管道和命名管道
一个进程把数据传入管道中,需要等待其他进程接受数据,如果没有其他进程接受数据,就会发数据的进程就会阻塞
-
信息队列
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
使用消息队列进行进程间通信,可能会收到数据块最大长度的限制约束等,这也是这种通信方式的缺点。如果频繁的发生进程间的通信行为,那么进程需要频繁地读取队列中的数据到内存,相当于间接地从一个进程拷贝到另一个进程,这需要花费时间。
-
共享内存
两个进程中各自有一块虚拟内存地址是映射到同一个真实物理地址中,这样两个进程就共享了同一块内存。这会涉及到内存竞争问题。所以引入信号量这个字段,信号量初始默认为1,表明没有进程正在读取该内存, 进程读取该共享内存之后,信号量变为0,进程B发现信号量为0,说明A正在读取,则会等待。
-
socket
这个就是我们一直在用的进程间的通信方式了,如我们的微信APP跟微信服务器通信,其实就是使用的Socket套接字进行通信的。
计算机请求缓存
-
强缓存 (本地验证,不发请求)
字段
expires(http1.0)
、cache-control(http1.1)
后者优先级高,二者的值为指定过期时间,cache-control
一般为时间戳,expires
一般为SUN , 17 Nov 2019 02:04:30 GMT
。如果未过期,状态码为200 或者 200OK 则表明强缓存命中private:客户端可以缓存 public:客户端和代理服务器均可缓存; max-age=xxx:缓存的资源将在 xxx 秒后过期,常见:2592000(三十天)、31536000(一年); no-cache:跳过当前强验证步骤,使用下一阶段协商缓存来验证是否过期; must-revalidate:如果强缓存时间过期,必须去服务器进行有效性检验这个旧的缓存 no-transform:多用于图片,不允许对资源进行转换压缩
-
协商缓存(服务器端验证,发请求)
字段有两组
Last-Modified + If-Modified-Since
:
上次请求时,缓存文件中的Last-Modified
记录该资源的修改日期,本次请求时,Last-Modified
会通过请求头If-Modified-Since
传递给服务器,服务器端的最新修改日期和该缓存文件的最新修改日期比较;ETag
+If-None-Match
:
ETag
是服务器端人为设置的一串唯一字符串(一般是哈希或者版本号),本次请求时,If-None-Match
传入缓存文件的ETag
值,去和服务器里的该资源的ETag
比较,以达到缓存验证的目的;
①
Last-Modified + If-Modified-Since
:
如果If-Modified-Since
值 就是 服务器端该文件的最新修改日期,说明缓存是新鲜的,服务器返回304,浏览器使用本地缓存;
如果If-Modified-Since
值 早于 服务器端该文件的最新修改日期,说明缓存不新鲜,服务器返回新的该资源,并且更新该资源Last-Modified
日期
②ETag
+If-None-Match
:
如果If-None-Match
传递给后台的缓存ETag
值和后台对应该文件的ETag
值一样,说明该缓存新鲜,服务器返回 304 状态码,浏览器使用本地缓存;
如果If-None-Match
传递给后台的缓存ETag
值和后台对应该文件的ETag
值不一样,说明该缓存不新鲜,服务器返回更新的资源和新的Etag
值;
算法
十进制转二进制
function digui(num){
let res = ''
if(Math.floor(num/2)!=0){
return er(Math.floor(num/2))+res+num%2
}else{
return res+num%2
}
}
function feidigui(num){
let res = ''
while(num>0){
if(Math.floor(num/2)!=0){
res = num%2 + res
num = Math.floor(num/2)
}else{
res = num%2 + res
break
}
}
return res
}
console.log(feidigui(10))//1010
解析url中的参数
// https://feedback.corp.kuaishou.com/ef/api/score/feedback/get/production/detail?nameEng=kalaxy&name=zlx&id=1function getParams(url){ let res = {} let arr = url.split('?') let paramsStr = arr[1] // nameEng=kalaxy&name=zlx&id=1 let params = paramsStr.split('&') params.forEach(item=>{ let obj = item.split('=') res[obj[0]] = obj[1] }) return res}
顺时针打印数组
let arr = [ [1,2,3,4,5], [14,15,16,17,6], [13,20,19,18,7], [12,11,10,9,8]]function fun(a,arr){ let res = [] let dep = arr.length let wid = arr[0].length if(a*2+2>Math.min(dep,wid)) return res for(let i=a;i<wid-a;i++){ res.push(arr[a][i]) } for(let i=a+1;i<dep-a-1;i++){ res.push(arr[i][wid-1-a]) } for(let i=wid-1-a;i>=a;i--){ res.push(arr[dep-1-a][i]) } for(let i=dep-2-a;i>a;i--){ res.push(arr[i][a]) } let A = a+1 let newa = fun(A,arr) res=res.concat(newa) return res }console.log(fun(0,arr))
Git
git commit 之后想撤回commit
git reset --soft HEAD^ //. HEAD~1
–mixed
意思是:不删除工作空间改动代码,撤销commit,并且撤销git add . 操作
这个为默认参数,git reset --mixed HEAD^ 和 git reset HEAD^ 效果是一样的。
–soft
不删除工作空间改动代码,撤销commit,不撤销git add .
–hard
删除工作空间改动代码,撤销commit,撤销git add .
注意完成这个操作后,就恢复到了上一次的commit状态。
顺便说一下,如果commit注释写错了,只是想改一下注释,只需要:
git commit --amend
此时会进入默认vim编辑器,修改注释完毕后保存就好了。