·React
1.Virtual dom(虚拟Dom)如何工作
相当于在dom的基础上建立一个抽象层,如果数据或者状态改变了,都会被同步到虚拟dom上,然后批量同步到dom上,虚拟dom(虚拟DOM具有批处理和高效的Diff算法)只对界面变化的部分进行实际的DOM操作
2.Vue和react的使用经验,两者的区别?***********
=> 相同点:
1.数据驱动页面,提供响应式的试图组件
2.都有virtual DOM,组件化的开发,通过props参数进行父子之间组件传递数据,都实现了webComponents规范
3.数据流动单向,都支持服务器的渲染SSR
4.都有支持native的方法,react有React native, vue有wexx
=> 不同点:
1.数据绑定:Vue实现了双向的数据绑定,react数据流动是单向的
2.数据渲染:大规模的数据渲染,react更快
3.使用场景:React配合Redux架构适合大规模多人协作复杂项目,Vue适合小快的项目
4.开发风格:react推荐做法jsx + inline style把html和css都写在js了
vue是采用webpack + vue-loader单文件组件格式,html, js, css同一个文件
(1)react整体是函数式的思想,把组件设计成纯组件,状态和逻辑通过参数传入,所以在react中,是单向数据流;
(2)vue的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立Watcher来监听,当属性变化的时候,响应式的更新对应的虚拟dom。
(3) 通过js来操作一切,还是用各自的处理方式(react通过js生成html,所以我们设计了jsx;js还会操作css,vue是把html,css,js组合到一起,用各自的处理方式)
3. react生命周期钩子的理解 用过啥钩子 有什么作用?*
react生命周期钩子的理解 用过啥钩子 有什么作用
react生命周期新旧对比
四个周期(初始化,挂载(componentWillMount挂载之前,render挂载中,componentDidMount挂载完成),更新(shouldComponentUpdate其中一个),卸载(componentWillUnmount))
react生命周期图谱
三个状态:Mounting(挂载时)(已插入真实的DOM)
Updating(更新时)(正在被重新渲染)
Unmounting(卸载时)(已移除真实的DOM)
componentDidMount 在第一次渲染后调用,只在客服端。之后组件已经生成对应的DOM结构,
componentDidUpdate 在组件完成更新后立即调用,在出初始化是不会调用
componentWillUnmount 组件卸载
4.React的性能优化********
(1)属性传递优化(在按钮内声明,在constructor内绑定);多组件优化,父组件优化子组件不优化的情况下(使用shouldComponentUpdate(nextProps, nextState);React.PureComponent 替换 React.Component:);
(2)context的运用不会逐层传递
5.reac性能优化是哪个周期函*******
shouldComponentUpdate 这个方法用来判断是否需要调用render方法重新描绘dom.因为dom的描绘非常消耗性能,
如果我们在shouldComponentUpdate方法中能够写出更优化的dom diff算法,可以极大的提高性能(shouldComponentUpdate函数是重新渲染时render()函数调用前被调用的函数,它接受两个参数:nexProps和nextState,分别表示下一个props和下一个state的值。并且,当函数返回false时候,阻止接下来的render()函数的调用,阻止组件的重渲染,而返回true时,组件照常重渲染)
6.react怎么划分业务组件和技术组件
(1)根据组件的职责通常把组件分为UI组件和容器组件
(2)UI组件负责UI的呈现,容器组件负责管理数据和逻辑
(3)两者通过React-redux提供connect方法联系起来
7.组件的状态
(包括:1.setState 实际做了什么?2.state 和 props 之间的区别是什么?3.为什么 setState 给了我一个错误的值?4.我应该如何更新那些依赖于当前的 state 的 state 呢?5.给 setState 传递一个对象与传递一个函数的区别是什么?6.setState 什么时候是异步的?7.为什么 React 不同步地更新 this.state?8.我应该使用一个像 Redux 或 MobX 那样的 state 管理库吗?)看完之后再进行之后题目
https://zh-hans.reactjs.org/docs/faq-state.html#what-does-setstate-do
8.react中的setState是同步还是异步还是?*
setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
合成事件:就是react 在组件中的onClick等都是属于它自定义的合成事件
原生事件:比如通过addeventListener添加的,dom中的原生事件
setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
state = { val: 0 }
batchUpdates = () => {
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
}
其实val只是1
9.给 setState 传递一个对象与传递一个函数的区别是什么?
传递一个函数可以让你在函数内访问到当前的state的值。因为setState的调用是分批的,所以你可以链式地进行更新,并确保他们是一个建立在另一个之上地,这样才不会发生冲突
import React , {Component} from 'react'
export default class product extends Component {
constructor(props){
super(props)
this.state={
count : 0,
color : 'red'
}
}
componentDidMount(){
document.title = `你点击了${this.state.count} 次`
}
componentDidUpdate(){
document.title = `你点击了${this.state.count} 次`
}
// this.setState((state) => {
// // 重要:在更新的时候读取 `state`,而不是 `this.state`。
// return {count: state.count + 1}
// });
clickHandle=()=>{
this.setState ((state)=>{ //clickHandle被调用四次count为4
return { count : state.count + 1}
})
}
clickNum = () =>{
this.clickHandle();
this.clickHandle();
this.clickHandle();
this.clickHandle();
}
render(){
return (
<div>
<p>点击了{this.state.count} 次</p>
<button onClick = {this.clickNum}>点击</button>
</div>
)
}
}
给 setState 传递一个对象时
import React , {Component} from 'react'
export default class product extends Component {
constructor(props){
super(props)
this.state={
count : 0,
color : 'red'
}
}
componentDidMount(){
document.title = `你点击了${this.state.count} 次`
}
componentDidUpdate(){
document.title = `你点击了${this.state.count} 次`
}
// this.setState((state) => {
// // 重要:在更新的时候读取 `state`,而不是 `this.state`。
// return {count: state.count + 1}
// });
clickHandle=()=>{ //clickHandle被调用四次count为1
this.setState ({count :this.state.count + 1})
}
clickNum = () =>{
this.clickHandle();
this.clickHandle();
this.clickHandle();
this.clickHandle();
}
render(){
return (
<div>
<p>点击了{this.state.count} 次</p>
<button onClick = {this.clickNum}>点击</button>
</div>
)
}
}
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
此代码可能会无法更新计数器:(setState传对象形式)
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
上面使用了箭头函数,不过使用普通的函数也同样可以:
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
这里的合并是浅合并,所以 this.setState({comments}) 完整保留了 this.state.posts, 但是完全替换了 this.state.comments。
关于前合并和深合并
10.setState*
setState通过一个队列机制实现state更新,当执行setState时,会将需要更新的state很后放入状态队列
而不会立即更新this.state,队列机制可以高效地批量更新state。如果不通过setState而直接修改this.state的值
那么该state将不会被放入状态队列中。当下次调用setState并对状态队列进行合并时,就会忽略之前修改的state,造成不可预知的错误
同时,也利用了队列机制实现了setState的异步更新,避免了频繁的重复更新state
同步更新state:
setState 函数并不会阻塞等待状态更新完毕,因此 setNetworkActivityIndicatorVisible 有可能先于数据渲染完毕就执行。
第二个参数是一个回调函数,在setState的异步操作结束并且组件已经重新渲染的时候执行
this.setState({
更新状态
parentId:category._id,
parentName:category.name
},()=>{//在状态更新且重新render后执行
// 获取二级分类列表
this.getCategorys()
})
也就是说,我们可以通过这个回调来拿到更新的state的值,实现代码的同步
例子:componentDidMount() {
fetch('https://test.com')
.then((res) => res.json())
.then(
(data) => {
this.setState({ data:data });
StatusBar.setNetworkActivityIndicatorVisible(false);
}
11.受控组件和非受控组件的区别*
受控组件:
(1)HTML中的表单元素是可输入的,也就是有自己的可变状态
(2)而React中可变状态通常保存在state中,并且只能通过setState() 方法来修改
(3)React讲state与表单元素值value绑定在一起,有state的值来控制表单元素的值
(4)受控组件:值受到react控制的表单元素
class App extends React.Component {
constructor(){
super()
this.inputChange = this.inputChange.bind(this)
}
state = {
txt : ''
}
inputChange(e){
this.setState({
txt: e.target.value
})
}
render(){
console.log(this.state);
return (
<div>
{/* 把state的值设置给输入框的value,绑定change事件,这样用户在输入内容的时候调用相应函数,在函数里面把当前设置的值赋值给state,从而达到数据的统一 */}
<input type="text" value={this.state.txt} onChange={this.inputChange}/>
</div>
)
}
}
ReactDOM.render(<App />,document.getElementById('root'))
非受控组件
(1)调用 React.createRef() 方法创建ref对象
(2)将创建好的 ref 对象添加到文本框中
(3)通过ref对象获取到文本框的值
(4)非受控组件: 表单组件没有value prop就可以称为非受控组件
class App extends React.Component {
constructor(){
super()
//创建 ref
this.txtRef = React.createRef()
}
// 获取文本框的值
getTxt =() => {
console.log(this.txtRef.current.value)
}
render(){
return (
<div>
<input type ="text" ref={this.txtRef} />
<button onClick ={this.getTxt}>获取值</button>
</div>
)
}
}
12.为什么虚拟 dom 会提高性能?
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。
13.react diff 原理(协调,diffing算法)
https://zh-hans.reactjs.org/docs/reconciliation.html#the-diffing-algorithm
上述链接总结得一下几个小点:
- 当对比两棵树时,React 首先比较两棵树的根节点。不同类型的根节点元素会有不同的形态。
- 对比不同类型的元素
- 对比同一类型的元素
- 对比同类型的组件元素
- 对子节点进行递归
- 为了解决对子节点进行递归产生的性能问题,我们React 引入了 key 属性。当子元素拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素。以下示例在新增 key 之后,使得树的转换效率得以提高
传统diff算法的时间复杂度为O(n^3), react却利用其特殊的diff算法做到了O(n3)到O(n)的飞跃性的提升
- 对应元素节点生成树形结构按照层级分解,只比较同级元素。
- 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
- 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。
- 给列表结构的每个单元添加唯一的 key 属性,方便比较。
- React 只会匹配相同class的component(这里面的class指的是组件的名字)
- 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个事件循环结束,
React检查所有标记 dirty 的 component 重新绘制. - 选择性子树渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能。
14.react中的refs
- 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其
current 属性。 - 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
- 你不能在函数组件上使用 ref 属性,因为他们没有实例
一般不将 DOM Refs 暴露给父组件
在极少数情况下,你可能希望在父组件中引用子节点的 DOM 节点。通常不建议这样做,因为它会打破组件的封装,但它偶尔可用于触发焦点或测量子 DOM 节点的大小或位置。
虽然你可以向子组件添加 ref,但这不是一个理想的解决方案,因为你只能获取组件实例而不是 DOM 节点。并且,它还在函数组件上无效。
15.为组件绑定this的四种方式(为函数绑定组件实例)
用react进行开发组件时,我们需要关注一下组件内部方法this的指向,react定义组件的方式有两种,一种为函数组件,一种为类组件,类组件内部可以定义一些方法,这些方法的this需要绑定到组件实例上
第一种方案,在构造函数内部使用bind绑定this,这样做的好处是,避免每次渲染时都要重新绑定,(在构造函数中绑定(ES2015))代码如下:
class Foo extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('Click happened');
}
render() {
return <button onClick={this.handleClick}>Click Me</button>;
}
}
第二种方案同样是用bind,但是这次不再构造函数内部使用,而是在render函数内绑定,但是这样的话,每次渲染都需要重新绑定,(在 Render 中的绑定)代码如下:
class Foo extends Component {
handleClick() {
console.log('Click happened');
}
render() {
return <button onClick={this.handleClick.bind(this)}>Click Me</button>;
}
}
第三种方案是在render函数中,调用方法的位置包裹一层箭头函数,因为箭头函数的this指向箭头函数定义的时候其所处作用域的this,而箭头函数在render函数中定义,render函数this始终指向组件实例,所以箭头函数的this也指向组件实例,代码如下:
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
以上这种方式有个小问题,因为箭头函数总是匿名的,如果你打算移除监听事件,是做不到的,更重要的一点是,如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法,那么怎么做才可以移除呢?看下一种方案
第四种方案是class 属性(第三阶段提案)实验性语法 class fields语法
class Foo extends Component {
// Note: this syntax is experimental and not standardized yet.
handleClick = () => {
console.log('Click happened');
}
render() {
return <button onClick={this.handleClick}>Click Me</button>;
}
}
16.事件处理函数如何传递参数(向事件处理程序传递参数)
在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 id 是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>**(使用箭头函数,e作为第二个参数传递,事件对象必须显式的进行传递)**
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>**(使用bind传递参数,事件对象以及更多的参数将会被隐式的隐藏进行传递)**
上述两种方式是等价的,分别通过箭头函数和 Function.prototype.bind 来实现。
在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。
17.React中的合成事件是什么?
合成事件是围绕浏览器原生事件充当跨浏览器包装器的对象。它们将不同浏览器的行为合并为一个 API。这样做是为了确保事件在不同浏览器中显示一致的属性。
除兼容所有浏览器外,它还拥有和浏览器原生事件相同的接口,包括 stopPropagation() 和 preventDefault()。
如果因为某些原因,当你需要使用浏览器的底层事件时,只需要使用 nativeEvent 属性来获取即可。
合成事件与浏览器的原生事件不同,也不会直接映射到原生事件。例如,在 onMouseLeave 事件中 event.nativeEvent 将指向 mouseout 事件。
18.React组件通讯如何实现
- 父组件向子组件通讯: 父组件可以向子组件通过传 props 的方式,向子组件进行通讯
- 子组件向父组件通讯:
props+回调的方式,父组件向子组件传递props进行通讯,此props为作用域为父组件自身的函数,子组件调用该函数, - 将子组件想要传递的信息,作为参数,传递到父组件的作用域中
- 兄弟组件通信: 找到这两个兄弟节点共同的父节点,结合上面两种方式由父节点转发信息进行通信
- 跨层级通信:Context( redux
)设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言,对于跨越多层的全局数据通过Context通信再适合不过
19.shouldComponentUpdate()和第五题类似
shouldComponentUpdate
仅检查了 props.color
或 state.count
是否改变。如果这些值没有改变,那么这个组件不会更新。(两个参数nextProps和nextState是否与当前值相等进行更新,返回ture渲染,返回false跳过渲染)
20.component和purecomponent有什么区别***********
PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate(),而Component没有。
Component是React App的基本构建的单位,也是React中的基本代码复用单位。PureComponent与Component在除了其shouldComponentUpdate方法的实现之外几乎完全相同。PureComponent已经替我们实现了shouldComponentUpdate方法。
对于PureComponent而言,当其props或者state改变之时,新旧props与state将进行浅对比(shallow comparison)。另一方面,Component默认的情况下其shouldComponentUpdate方法并不进行新旧props与state的对比。
那么,此处提及的shallow comparison究竟是什么呢?
对于基本类型(primitives),例如数字或者布尔值,来说,浅拷贝将会检查其值是否相同,例如1与1相等,true与true相等。对于引用类型的变量,例如复杂的javascript对象或者数组,来说,浅拷贝将仅仅检查它们的引用值是否相等。这意味着,对于引用类型的变量来说,如果我们只是更新了其中的一个元素,例如更新了数组中某一位置的值,那么更新前后的数组仍是相等的。
因此意味着相比于Component,PureCompoent的性能表现将会更好。但使用PureCompoent要求满足如下条件:
props和state都必须是不可变对象(immutable object)。
props和state不能有层级嵌套的结构,(否则对子层级的改变无法反映在浅拷贝中)。
如果数据改变无法反应在浅拷贝上,则应该调用forceUpdate来更新Component。
一个PureComponent的子Component也应当是PureComponent。
—为什么有的时候更改原数据,但是却没有purecomponent呢却没有改变呢?)
因为我们修改了原来的props和state的值,也许这个时候另一个地方正用到它们两呢,所以这里没生效
要想改变的话,我们不可以改变原来的数据,我们需要避开正在使用的props和state,所以使用旧数据创建新数据时,要保证旧数据同时可用且不变,我们就用我们自己创建的新的数据,purecomponent就生效了,有助于组件重新渲染。如果是简单的赋值给一个新的变量,新的对象和旧的对象只是名称不同,实际上占用了同样的内存地址仅仅名称不同,这对react响应重新渲染造成了性能影响,或不能及时更新dom。
附上地址更容易理解:https://react.docschina.org/docs/optimizing-performance.html#shouldcomponentupdate-in-action
21.(在构造函数中)调用 super(props) 的目的是什么?
- 在super()被调用之前,子类是不能使用this的,在ES2015中子类必须在constructor中调用super()。
- 传递props给super()的原因则是便于(在子类中)能在constructor访问this.props。
22.简述 flux 思想
- Flux 的最大特点,就是数据的"单向流动"。
- 用户访问 View
- View 发出用户的 Action
- Dispatcher 收到 Action,要求 Store 进行相应的更新
- Store 更新后,发出一个"change"事件
- View 收到"change"事件后,更新页面
23.Redux与Flux有何不同
Flux | Redux |
---|---|
1.Store包含状态和更改逻辑 | 1.Store和更改逻辑是分开的 |
2.有多个Store | 2.只有一个Store |
3.所有 Store 都互不影响且是平级的 | 3. 带有分层 reducer 的单一 Store |
4. 有单一调度器 | 4. 没有调度器的概念 |
5. React 组件订阅 store | 5. 容器组件是有联系的 |
6. 状态是可变的 | 6. 状态是不可改变的 |
24.节流和防抖******
js节流和防抖
react节流和防抖
为什么要用到节流和防抖?
如果事件处理函数调用的次数不限制,会家中浏览器的负担,导致用户体验非常差
函数防抖:
函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。如下图,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。
可以参考javascript中文文档关于debounce 函数(防抖)※※
有时,我们不希望回调函数被频繁调用。比如,用户填入网页输入框的内容,希望通过 Ajax 方法传回服务器,jQuery 的写法如下。
$('textarea').on('keydown', ajaxAction);
这样写有一个很大的缺点,就是如果用户连续击键,就会连续触发keydown事件,造成大量的 Ajax 通信。这是不必要的,而且很可能产生性能问题。正确的做法应该是,设置一个门槛值,表示两次 Ajax 通信的最小间隔时间。如果在间隔时间内,发生新的keydown事件,则不触发 Ajax 通信,并且重新开始计时。如果过了指定时间,没有发生新的keydown事件,再将数据发送出去。
这种做法叫做 debounce(防抖动)。假定两次 Ajax 通信的间隔不得小于2500毫秒,上面的代码可以改写成下面这样。
$('textarea').on('keydown', debounce(ajaxAction, 2500));
function debounce(fn, delay){
var timer = null; // 声明计时器
return function() {
var context = this;
var args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
}
上面代码中,只要在2500毫秒之内,用户再次击键,就会取消上一次的定时器,然后再新建一个定时器。这样就保证了回调函数之间的调用间隔,至少是2500毫秒。
函数节流:
函数节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。如下图,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。
25.react中的keys******
帮助我们跟踪哪些项目已更改、添加、从列表中删除,key是独一无二的,可以让我们高效的去定位元素,并且操作它
如果把索引当作keys
如果把索引当作keys,那么当删除或者添加新的dom节点会页面错乱
26.useEffect第二个参数****
----useEffect中的return相当于模拟componentWillUnmomt****
1、当useEffect没有第二个参数时
const [count, setCount] = useState(0)
useEffect(() => {
console.log("useEffect:", count)
})
组件的初始化和更新都会执行
2、空数组
const [count, setCount] = useState(0)
useEffect(() => {
console.log("useEffect:", count)
}, [])
初始化调用一次之后不再执行,相当于componentDidMount
3、有一个或者多个值的数组
const [count1, setCount1] = useState(0)
const [count2, setCount2] = useState(0)
useEffect(() => {
console.log(count1, count2)
}, [count1, count2])
传入第二个参数,只有一个值,该值有变化就执行,
传入第二个参数,有多个值的数组,会比较每一个值,有一个不相等就执行
27.MVC模式在react模块中分别指哪个模块?*****************
mvc模式同时提供了对html,css 和JavaScript的完全控制
model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
view(视图)是应用程序中处理数据显示的部分,通常视图是依据模型数据创建的。
controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。(js)
28.jQuery和React各自的优缺点*************
jquery和react各自优缺点
(1)编程思想不同
jquery开发:监听事件=>判断当前业务状态=>直接操作dom元素
react开发:监听事件=>判断当前业务状态=>修改state=>render修改dom元素(最小化修改)
(2)优缺点比较
jquery缺点:在逻辑复杂的情况下,我们需要花很大的精力来理清各种业务逻辑之间的联系。并且直接操作dom,难以写出优雅的代码。且代码耦合度高,难以维护。
jquery优点: 门槛低易上手,只是简单页面可以快速开发完成。
react缺点:学习成本高难以上手,类似我们公司的情况,前期没有组件积累,开发缓慢。
react优点: 页面由于是以组件的形式拆分,相对于jquery来说,耦合性比较低,较好维护。
29.redux原理流程*************
redux实现原理解析具体分析
redux是将整个应用状态存储到一个地方上称为store,里面保存和一个状态树store tree,组件可以派发(dispatch)行为(action)给store,而不是直接通知其他组件,组件内部通过订阅store中的状态state来刷新自己的视图。
30.hook相对于class的优点*******
hook优点
1、跨组件复用: 其实 render props 和 高阶函数 也是为了复用,相比于它们,Hook 作为官方的底层 API,最为轻量,而且改造成本小,不会影响原来的组件层次结构和传说中的嵌套地狱;
2、类定义更为复杂: 不同的生命周期会使逻辑变得分散且混乱,不易维护和管理;时刻需要关注this的指向问题;代码复用代价高,高阶组件的使用经常会使整个组件树变得臃肿;
3、状态与UI隔离: 正是由于 Hook 的特性,状态逻辑会变成更小的粒度,并且极容易被抽象成一个自定义 Hook,组件中的状态和 UI 变得更为清晰和隔离。
·js
1.js的继承*
(1)原型链继承
核心: 将父类的实例作为子类的原型
(2)构造继承
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类
(3)实例继承
核心:为父类实例添加新特性,作为子类实例返回
(4)拷贝继承
(5)组合继承
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现 函数复用
(6)寄生组合继承
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实 例方法/属性,避免的组合继承的缺点
2.请描述一下cookies,sessionStorage和localStorage的区别***
(1)cookies存储在用户本地终端上,通常是要加密的,会在服务器与浏览器之间来回传递(可以存储用户是否登录的状态,发送给后端。)存储数据大小不能超过4k,sessionStorage和localStorage可以达到5M
(2)sessionStorage 数据在当前浏览器窗口关闭后自动删除.
(3)localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据
(4)cookie 设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
3.跳出for循环 *
for…of…
4.es6的新特性都有哪些?**
(1)let定义块级作用域变量 没有变量的提升,必须先声明后使用 let声明的变量,不能与前面的let,var,conset声明的变量重名
(2)const 定义只读变量 const声明变量的同时必须赋值,const声明的变量必须初始化,一旦初始化完毕就不允许修改 const声明变量也是一个块级作用域变量 const声明的变量没有“变量的提升”,必须先声明后使用 const声明的变量不能与前面的let, var , const声明的变量重 const定义的对象\数组中的属性值可以修改,基础数据类型不可以
(3)ES6可以给形参函数设置默认值
(4)在数组之前加上三个点(…)展开运算符
(5)数组的解构赋值、对象的解构赋值
(6)箭头函数的特点 箭头函数相当于匿名函数,是不能作为构造函数的,不能被new 箭头函数没有arguments实参集合,取而代之用…剩余运算符解决 箭头函数没有自己的this。他的this是继承当前上下文中的this 箭头函数没有函数原型 箭头函数不能当做Generator函数,不能使用yield关键字 不能使用call、apply、bind改变箭头函数中this指向 Set数据结构,数组去重
5.同步任务和异步任务
参考链接更容易理解
同步任务: 那些没有被引擎挂起、在主线程上排队执行的任务,目前只有前一个任务执行完毕,才能执行后一个任务。
异步任务: 那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎任务某个任务可以执行了(比如Ajax操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有“堵塞”效应。
举例来说,Ajax 操作可以当作同步任务处理,也可以当作异步任务处理,由开发者决定。如果是同步任务,主线程就等着 Ajax 操作返回结果,再往下执行;如果是异步任务,主线程在发出 Ajax 请求以后,就直接往下执行,等到 Ajax 操作有了结果,主线程再执行对应的回调函数。
6.任务队列和事件循环机制
参考链接更容易理解
任务队列: javascript运行时,除了一个正在运行的主线程外,还提供了一个任务队列,里面是各种需要当前程序处理的异步任务。首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列,也就是说,不会重新进入主线程,因为没有用回调函数指定下一步的操作。
事件循环机制: JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了
7.setTimeout()和setInterval()方法的区别以及setTimeout 和 setInterval的机制?和setTimeout(f,0)的几个重要的用途*
setTimeout()和setInterval()方法的区别
setTimeout()是延时器,setInterval()是定时器。setTimeout(表达式,延时时间)在执行时,是在载入后延迟指定时间后,去执行一次表达式,记住,次数是一次,而setInterval(表达式,交互时间)则不一样,它从载入后,每隔指定的时间就执行一次表达式 所以,完全是不一样的。被调用或窗口被关闭。)
setTimeout 和 setInterval的机制
关于setTimeout和setInterval的运行机制的javascript的中文文档
setTimeout和setInterval的运行机制,是将指定的代码移出本轮事件循环,等到下一轮事件循环,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就继续等待。
这意味着,setTimeout和setInterval指定的回调函数,必须等到本轮事件循环的所有同步任务都执行完,才会开始执行。由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证,setTimeout和setInterval指定的任务,一定会按照预定时间执行。
setTimeout(someTask, 100);
veryLongTask();
上面代码的setTimeout,指定100毫秒以后运行一个任务。但是,如果后面的veryLongTask函数(同步任务)运行时间非常长,过了100毫秒还无法结束,那么被推迟运行的someTask就只有等着,等到veryLongTask运行结束,才轮到它执行。
再看一个setInterval的例子。
setInterval(function () {
console.log(2);
}, 1000);
sleep(3000);
function sleep(ms) {
var start = Date.now();
while ((Date.now() - start) < ms) {
}
}
上面代码中,setInterval要求每隔1000毫秒,就输出一个2。但是,紧接着的sleep语句需要3000毫秒才能完成,那么setInterval就必须推迟到3000毫秒之后才开始生效。注意,生效后setInterval不会产生累积效应,即不会一下子输出三个2,而是只会输出一个2。
因为js是单线程的。浏览器遇到etTimeout 和 setInterval会先执行完当前的代码块,在此之前会把定时器推入浏览器的
待执行时间队列里面,等到浏览器执行完当前代码之后会看下事件队列里有没有任务,有的话才执行定时器里的代码
setTimeout(f,0)的几个重要用途:
- 网页开发中,某个事件先发生在子元素,然后冒泡到父元素,即子元素的事件回调函数,会早于父元素的事件回调函数触发。如果,想让父元素的事件回调函数先发生,就要用到setTimeout(f,
0),子元素用setTimeout(f,0)将被推迟到下一轮事件循环中执行。 - 用户自定义的回调函数,通常在浏览器的默认动作之前触发。比如,用户在输入框输入文本,keypress事件会在浏览器接收文本之前触发。用户自定义的回调函数用setTimeout(f,0)将被推迟到下一轮事件循环中执行。
- 因为 JavaScript 执行速度远高于 DOM,会造成大量 DOM 操作“堆积”,用setTimeout(f, 0)就不会。
8. Promise与setTimeout异步执行顺序问题*
Promise为异步编程,setTimeout 类似异步,但是同步,promise与setTimeout两者的执行顺序孰先孰后呢?
setTimeout(() =>{
console.log('延时器'); // 执行顺序3
}, 0);
new Promise((resolve, reject) => {
resolve('promise');
}).then(console.log); // // 执行顺序2
console.log(123); // 执行顺序1
new Promise: 如果里面有打印,刚建就打印,相当于本轮的同步任务
console.log(123);是同步任务本轮事件循环中就执行。
promise.then是异步的,但是不是正常的异步任务,而是微任务。微任务追加到本轮事件循环中。所以微任务的执行时间早于正常任务。
setTimeout是类似异步。会被追加到下一轮事件循环队列中执行。
任务队列和事件循环等
javascript中文文档更容易辅助理解
下面理解过程:
promise.then和setTimeout都是异步的,那么在事件循环队列中 promise.then的事件应该排在setTimeout后面,可为什么promise.then却在setTimeout前面被输出?
这牵扯到一个概念:
event loop
Javascript是单线程的,所有的同步任务都会在主线程中执行。
当主线程中的任务,都执行完之后,系统会 “依次” 读取任务队列里的事件。与之相对应的异步任务进入主线程,开始执行。
异步任务之间,会存在差异,所以它们执行的优先级也会有区别。大致分为 微任务(micro
task,如:Promise、MutaionObserver等)和 宏任务(macro
task,如:setTimeout、setInterval、I/O等)。
Promise 执行器中的代码会被同步调用,但是回调是基于微任务的。
个人理解: Promise 的回调函数属于异步任务,会在同步任务之后执行。
但是,Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务。
9.promise封装一个异步请求(代码题)
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
上面代码中,getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。
10.简单说一下Promise(和Promise处理异步题目在下面)
Promise是异步编程的一种方案,简单来说就是Promise就是一个容器,里面保存着一个未来才会结束的事件(通常是异步操作)的结果。从语法上来讲Promise是一个对象,从它可以获取异步操作的消息。Promise提供同一个api,各种异步操作都可以使用同样的方法处理。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
11.promise.all()和promise.race()的区别
看文档理解更深刻
Promise.all()和Promise.race()都是将多个Promise实例,包装成一个新的Promise实例
Promise.all()
Promise.all() 比如当数组里的P1,P2都执行完成时,页面才显示。值得注意的是,返回的数组结果顺序不会改变,及时p2的返回要比p1的返回快,顺序依然是p1,p2;Promise.all成功返回成功数组,失败返回失败数据,一旦失败就不会继续往下走。
Promise.race()
Promise.race()中的race是赛跑的意思,也就是说Promise.race([p1,p2,p3])里面的结果哪个获取的快,就返回哪个结果,不管结果本身是成功还是失败。
使用场景:
Promise.all和Promise.race都是有使用场景的。
有些时候我们做一个操作可能得同时需要不同的接口返回的数据,这时我们就可以使用Promise.all;
有时我们比如说有好几个服务器的好几个接口都提供同样的服务,我们不知道哪个接口更快,就可以使用Promise.race,哪个接口的数据先回来我们就用哪个接口的数据。
12.JavaScript和TypeScript*
13.JavaScript有几种类型的值?,你能画一下他们的内存图吗?*
**栈:原始数据类型(Undefined,Null,Boolean,Number、String)**
**堆:引用数据类型(Object、Array和Function)两种类型的区别是:存储位置不同**;
1,原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
2,引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体
14.js的基本数据类型(主要)
javascript的每一个值,都属于某一种数据类型。javascript的数据类型一共有六种(ES6又增加了第七种symbol类型的值)。
- 数值(number):整数和小数(比如:1和3.14)
- 字符串(string):文本(比如:hello world)
- 布尔值(boolean):表示真伪的两个特殊的值,即true(真)和false(假)
- undefined:表示‘未定义’或不存在,即由于目前没有定义,所以此处暂时没有任何值
- null:表示空值,此处的值为空
- 对象:各种值组成的集合
原始数类型(基本的数据类型):数值,字符串,布尔
合成类型:对象(包括狭义的对象(object),数组(array),函数(function))
两个特殊的值:undefined和null
15.var、let、const区别,let,const比var的好处********
-----for循环中let和var的区别********
https://blog.csdn.net/unionz/article/details/80032048
(1)var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
(2)let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
(3)const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。
<script type="text/javascript">
// 块作用域
{
var a = 1;
let b = 2;
const c = 3;
// c = 4; // 报错
var aa;
let bb;
// const cc; // 报错
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
console.log(aa); // undefined
console.log(bb); // undefined
}
console.log(a); // 1
// console.log(b); // 报错
// console.log(c); // 报错
// 函数作用域
(function A() {
var d = 5;
let e = 6;
const f = 7;
console.log(d); // 5
console.log(e); // 6
console.log(f); // 7
})();
// console.log(d); // 报错
// console.log(e); // 报错
// console.log(f); // 报错
</script>
for循环中let和var的区别,里面涉及到一些闭包的问题
https://blog.csdn.net/m0_37816134/article/details/100053012
var在的那个for循环,var是全局变量,因为es5的时候还是没有块级作用域的概念,每次循环var的全局变量就会被重新赋值;
let在那个for循环中,let是局部变量,因为es6的时候是有块级作用域的概念,每次循环都是不同的块级作用域,所以不存在重复声明的问题
16.typeof的用法,typeof运算符和instanceof运算符的区别和isPrototypeOf()方法
typeof运算符可以返回一个值的数据类型。
数值,字符串,布尔值分别返回number,string,boolean
typeof 123 // "number"
typeof '123' // "string"
typeof false // "boolean"
函数返回function
function f() {}
typeof f
// "function"
undefined返回undefined
typeof undefined
// "undefined"
利用这点typeof可以检查一个没声明的变量,而不报错
v
// ReferenceError: v is not defined
typeof v
// "undefined"
上面代码中,变量v没有用var命令声明,直接使用就会报错。但是,放在typeof后面,就不报错了,而是返回undefined
实际编程中,这个特点通常用在判断语句。
// 错误的写法
if (v) {
// ...
}
// ReferenceError: v is not defined
// 正确的写法
if (typeof v === "undefined") {
// ...
}
对象返回object。
typeof window // "object"
typeof {} // "object"
typeof [] // "object"
上面代码中,空数组([])的类型也是object,这表示在 JavaScript 内部,数组本质上只是一种特殊的对象。(上一题)
typeof运算符和instanceof运算符的区别
typeof(空数组([])的类型也是object,这表示在 JavaScript 内部,数组本质上只是一种特殊的对象,即数组和对象都返回object)(function返回function)
typeof {} // "object"
typeof [] // "object"
instanceof(可以判断是数组还是对象)
var o = {};
var a = [];
o instanceof Array // false
a instanceof Array // true
上述a instanceof Array // true 表示 Array是否是a的构造函数(它会检查右边构造函数的原型对象(prototype),是否在左边对象的原型链上)
v instanceof Vehicle
// 等同于
Vehicle.prototype.isPrototypeOf(v)
上面代码中,Vehicle是对象v的构造函数,它的原型对象是Vehicle.prototype,isPrototypeOf()方法是 JavaScript 提供的原生方法,用于检查某个对象是否为另一个对象的原型
17.null 和undefined的区别
null:表示空值,此处的值为空,转为数值为0。调用函数值时某个参数未设置任何值,这是就可以传入null,表示该参数为空。比如,某个函数接收引擎抛出的错误作为参数,如果运行过程中为出现错误,那么这个参数就会传入null,表示为发生错误;
undefined:表示‘此处无定义’的原始值。转为数值时为NaN。下面是返回undefined的典型场景。
// 变量声明了,但没有赋值
var i;
i // undefined
// 调用函数时,应该提供的参数没有提供,该参数等于 undefined
function f(x) {
return x;
}
f() // undefined
// 对象没有赋值的属性
var o = new Object();
o.p // undefined
// 函数没有返回值时,默认返回 undefined
function f() {}
f() // undefined
18.javascript循环语句
- while循环:while (条件) {语句;}
- for循环:for (初始化表达式; 条件; 递增表达式) {语句}
- do …while 循环:do {语句} while (条件);
- break语句和continue 语句:break语句用于跳出代码块或循环;continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环。
19.js遍历*********
for:for(let i=0; i<10; i++)i:下标 arr[i]:值
for…each: arr.forEach((item,index) => {}) 遍历数组,不能使用break,contintue,return
for…in:for (var i in arr){ // i是下标(索引)} i:下标 arr[i]:值 原型链上的所有属性都将被访问 arr.hasOwnProperty(i)可以判断是否在实例上,在实例上才会被返回
for…of: 只能遍历数组,可以break跳出循环
map: 有返回值,可以返回结果数组,不能遍历对象,只能遍历数组或对象数组
遍历方法比较
1、以数组为例,JavaScript 提供多种遍历语法。最原始的写法就是for循环。
for (var index = 0; index < myArray.length; index++) {
console.log(myArray[index]);
}
2、这种写法比较麻烦,因此数组提供内置的forEach方法。
myArray.forEach(function (value) {
console.log(value);
});
这种写法的问题在于,无法中途跳出forEach循环,break命令或return命令都不能奏效。
3、for…in循环可以遍历数组的键名。
for (var index in myArray) {
console.log(myArray[index]);
}
for…in循环有几个缺点。
数组的键名是数字,但是for…in循环是以字符串作为键名“0”、“1”、“2”等等。
for…in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
某些情况下,for…in循环会以任意顺序遍历键名。
总之,for…in循环主要是为遍历对象而设计的,不适用于遍历数组。
4、for…of循环相比上面几种做法,有一些显著的优点。
for (let value of myArray) {
console.log(value);
}
有着同for…in一样的简洁语法,但是没有for…in那些缺点。
不同于forEach方法,它可以与break、continue和return配合使用。
提供了遍历所有数据结构的统一操作接口。
下面是一个使用 break 语句,跳出for…of循环的例子。
for (var n of fibonacci) {
if (n > 1000)
break;
console.log(n);
}
上面的例子,会输出斐波纳契数列小于等于 1000 的项。如果当前项大于 1000,就会使用break语句跳出for…of循环。
20.块级作用域
ES5 中作用域有:全局作用域、函数作用域。没有块作用域的概念。
ES6 中新增了块级作用域。块作用域由 { } 包括,if语句和 for语句里面的{ }也属于块作用域。
<script type="text/javascript">
{
var a = 1;
console.log(a); // 1
}
console.log(a); // 1
// 通过var定义的变量可以跨块作用域访问到。
(function A() {
var b = 2;
console.log(b); // 2
})();
// console.log(b); // 报错,
// 可见,通过var定义的变量不能跨函数作用域访问到
if(true) {
var c = 3;
}
console.log(c); // 3
for(var i = 0; i < 4; i ++) {
var d = 5;
};
console.log(i); // 4 (循环结束i已经是4,所以此处i为4)
console.log(d); // 5
// if语句和for语句中用var定义的变量可以在外面访问到,
// 可见,if语句和for语句属于块作用域,不属于函数作用域。
</script>
21.函数作用域
作用域指的是变量存在的范围。
在ES5规范中,两种作用域:①一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;②另一种是函数作用域,变量只在函数内部存在
ES6增加了块级作用域
对于顶层函数来说,函数外部声明的变量就是全局变量,它可以在函数内部读取
var v = 1;
function f() {
console.log(v);
}
f()
// 1
上面的代码表明,函数f内部可以读取全局变量v
在函数内部定义的变量,外部无法读取,成为局部变量
function f(){
var v = 1;
}
v // ReferenceError: v is not defined
上面代码中,变量v在函数内部定义,所以是一个局部变量,函数之外就无法读取。
函数内部的变量会在该作用域内覆盖同名全局变量
var v = 1;
function f(){
var v = 2;
console.log(v);
}
f() // 2
v // 1
上面代码中,变量v同时在函数的外部和内部有定义。结果,在函数内部定义,局部变量v覆盖了全局变量v。
注意:对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量
if (true) {
var x = 5;
}
console.log(x); // 5
上面代码中,变量x在条件判断区块之中声明,结果就是一个全局变量,可以在区块之外读取。
函数内部的变量提升:
与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
function foo(x) {
if (x > 100) {
var tmp = x - 100;
}
}
// 等同于
function foo(x) {
var tmp;
if (x > 100) {
tmp = x - 100;
};
}
函数本身的作用域:
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2。
总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
var x = function () {
console.log(a);
};
function y(f) {
var a = 2;
f();
}
y(x)
// ReferenceError: a is not defined
上面代码将函数x作为参数,传入函数y。但是,函数x是在函数y体外声明的,作用域绑定外层,因此找不到函数y的内部变量a,导致报错。
同样的,函数体内部声明的函数,作用域绑定函数体内部。
function foo() {
var x = 1;
function bar() {
console.log(x);
}
return bar;
}
var x = 2;
var f = foo();
f() // 1
上面代码中,函数foo内部声明了一个函数bar,bar的作用域绑定foo。当我们在foo外部取出bar执行时,变量x指向的是foo内部的x,而不是foo外部的x。正是这种机制,构成了下文要讲解的“闭包”现象。
22.闭包和闭包造成的内存泄露如何解决******
– --列举闭包的例子*******?
辅助性理解:(可以阅读上一题做参考)理解闭包必须理解变量作用域,前面提到javascript有两个作用域:全局作用域和函数作用域。函数内部可以直接读取全局变量。
var n = 999;
function f1() {
console.log(n);
}
f1() // 999
上面代码中,函数f1可以读取全局变量n。
但是,正常情况下,函数外部无法读取函数内部声明的变量。
function f1() {
var n = 999;
}
console.log(n)
// Uncaught ReferenceError: n is not defined(
上面代码中,函数f1内部声明的变量n,函数外是无法读取的。
如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。
function f1() {
var n = 999;
function f2() {
console.log(n); // 999
}
}
上面代码中,函数f2就在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是 JavaScript 语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
上面代码中,函数f1的返回值就是函数f2,由于f2可以读取f1的内部变量,所以就可以在外部获得f1的内部变量了。
闭包就是函数f2,即能够读取其他函数内部变量的函数。由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
上面代码中,start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。
为什么闭包能够返回外层函数的内部变量?原因是闭包(上例的inc)用到了外层变量(start),导致外层函数(createIncrementor)不能从内存释放。只要闭包没有被垃圾回收机制清除,外层函数提供的运行环境也不会被清除,它的内部变量就始终保存着当前值,供闭包读取。
闭包的另一个用处,是封装对象的私有属性和私有方法
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name,
getAge: getAge,
setAge: setAge
};
}
var p1 = Person('张三');
p1.setAge(25);
p1.getAge() // 25
上面代码中,函数Person的内部变量_age,通过闭包getAge和setAge,变成了返回对象p1的私有变量。
注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。
闭包的解释和作用特性:
闭包就是在函数里面声明函数,本质上说就是在函数内部和函数外部搭建起一座桥梁,使得子函数可以访问父函数中所有的局部变量,这就是 JavaScript 语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。但是反之不可以,这就是闭包的解释及作用之一。
第二个作用,则是保护变量不受外界污染,使其一直存在内存中,就是闭包可以使得它的变量环境一直存在。
第三个作用就是,封装对象的私有属性和私有方法。
闭包为什么能返回外层函数的内部变量:
原因就是闭包用到了外层变量,导致外层函数不能从内存中释放。只要闭包没有被垃圾回收机制清除,外层函数提供的运行环境也不会被清除,它的内存变量就始终保存着当前的值,共闭包读取。
闭包的缺点:
外层函数每次运行都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。
闭包造成的内存泄露将如何解决?
闭包造成的内存泄露如何解决
退出函数之前将不用的函数局部变量全部销毁
闭包的实例
js闭包总结包含闭包的例子详解
我们要实现一个例子点击浏览器中的五个按钮可以在控制台上打印对应的序号;
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
<button>5</button>
<script>
let btns = document.querySelectorAll('button');
for (let i = 0; i < 5; i++) {
btns[i].onclick = (function (i) {
return function (){
console.log(i + 1);
}
})(i)
}
</script>
当前执行代码的时候,同样的首先执行for循环,每次循环的i(立即执行函数的实参)的值都会通过参数传递到立即执行函数成为内部的局部变量i(立即执行函数的形参)中,由于内部是闭包函数,所以传入的i会保存在内存当中,供闭包函数的使用当我们运行之后,点击按钮触发内部的闭包函数,由于闭包函数作用域当中并没有定义的变量i,所以它按作用域链向上搜索i,找到立即执行函数中保存的局部变量i进行引用,最终实现我们想要的效果
第二种方法:
let btns = document.querySelectorAll('button');
for(var i = 0 ;i<5;i++){
btns[i].onclick= function(){
console.log(i+1);
}
}
首先,通过let定义变量使得for循环当中也成为块级作用域,然后内部有一个需要通过事件来触发的函数,这样我们就形成了一个类似于闭包的形式。所以运行代码之后,每次循环得到的局部变量i值会同样的保存在内存当中不会销毁,然后当我们点击按钮触发之后执行内部的函数,同样由于内部的函数没有定义变量i,就会就近寻找到for循环这个块级作用域当中的局部变量i进行引用,最后同样得到了我们想要的结果
23.javascript中eval命令
eval命令接受一个字符串作为参数,并将这个字符串当作语句执行。
eval('var a = 1;');
a // 1
上面代码将字符串当作语句运行,生成了变量a。
如果参数字符串无法当作语句运行,那么就会报错。
eval('3x') // Uncaught SyntaxError: Invalid or unexpected token
放在eval中的字符串,应该有独自存在的意义,不能用来与eval以外的命令配合使用。举例来说,下面的代码将会报错。
eval('return;'); // Uncaught SyntaxError: Illegal return statement
上面代码会报错,因为return不能单独使用,必须在函数中使用。
如果eval的参数不是字符串,那么会原样返回。
eval(123) // 123
eval没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安全问题。
var a = 1;
eval('a = 2');
a // 2
上面代码中,eval命令修改了外部变量a的值。由于这个原因,eval有安全风险。
为了防止这种风险,JavaScript 规定,如果使用严格模式,eval内部声明的变量,不会影响到外部作用域。
(function f() {
'use strict';
eval('var foo = 123');
console.log(foo); // ReferenceError: foo is not defined
})()
上面代码中,函数f内部是严格模式,这时eval内部声明的foo变量,就不会影响到外部。
不过,即使在严格模式下,eval依然可以读写当前作用域的变量。
(function f() {
'use strict';
var foo = 1;
eval('foo = 2');
console.log(foo); // 2
})()
上面代码中,严格模式下,eval内部还是改写了外部变量,可见安全风险依然存在。
总之,eval的本质是在当前作用域之中,注入代码。由于安全风险和不利于 JavaScript 引擎优化执行速度,一般不推荐使用。通常情况下,eval最常见的场合是解析 JSON 数据的字符串,不过正确的做法应该是使用原生的JSON.parse方法。
24.const定义的对象属性是否可以改变
const person = {
name : 'jiuke',
sex : '男'
}
person.name = 'test'
console.log(person.name)
运行上述代码,发现person对象的name属性确实被修改了,这是怎么回事呢?
因为对象是引用类型的,person中保存的仅是对象的指针,这就意味着,const仅保证指针不发生改变,修改对象的属性不会改变对象的指针,所以是被允许的。也就是说const定义的引用类型只要指针不发生改变,其他的不论如何改变都是允许的。
然后我们试着修改一下指针,让person指向一个新对象,果然报错
const person = {
name : 'jiuke',
sex : '男'
}
person = {
name : 'test',
sex : '男'
}
25.简单实现一个queryString,具有parse和stringify的能力
/**
* 简单实现一个queryString,具有parse和stringify的能力,
* parse,用于把一个URL查询字符串解析成一个键值对的集合。
* 输入:查询字符串 'foo=bar&abc=xyz&abc=123'
* 输出:一个键值对的对象
* {
* foo: 'bar',
* abc: ['xyz', '123'],
* }
* stringify,相反的,用于序列化给定对象的自身属性,生成URL查询字符串。
* 输入:一个键值对的对象
* {
* foo: 'bar',
* abc: ['xyz', '123'],
* }
* 输出:查询字符串 'foo=bar&abc=xyz&abc=123'
*/
方法一:
const queryString = {
parse() {
/* 功能实现 */
let arg = arguments[0];
let obj = {}
let arr = arg.split("&")
arr = arr.map(item => {
return item.split("=");
})
for(let i=0,len=arr.length;i<len;i++){
let item = arr[i][0];
if(obj[item]){
obj[item] = [obj[item]].concat([arr[i][1]])
}else{
obj[arr[i][0]]=arr[i][1]
}
}
return obj;
},
stringify() {
/* 功能实现 */
let obj = arguments[0]
let str = ""
var arr = []
for(let item in obj){
if(Array.isArray(obj[item])){
let m= obj[item].map(i=>{
return item+"="+i
})
arr = arr.concat(m)
}else{
arr.push(item+"="+obj[item])
}
}
str += arr.join("&")
return str
},
};
console.log(queryString.parse('foo=bar&abc=xyz&abc=123')); // { foo: 'bar', abc: [ 'xyz', '123' ] }
var res = {
foo: 'bar',
abc: ['xyz', '123'],
}
console.log(queryString.stringify(res)) // foo=bar&abc=xyz&abc=123
26. forEach与map的区别与用法,forEach和map能不能用break跳出循环*************
一、相同点
(1)都是循环数组的每一项
(2)每次执行匿名函数都会执行三个参数,参数分别是item(当前每一项),index(索引值),arr(原数组)
(3)匿名函数的this都是指向window
(4)只能遍历数组
array.map(function(item,index,arr){},this)
Array.forEach(function(item,index,arr){},this)
二、不同点
(1)map()会分配内存空间存储新数组并返回,forEach()不会返回数据。
(2)forEach()允许callback更改原始数组的元素。map()返回新的数组。
1、forEach()
forEach()针对每一个元素执行提供的函数,对数据的item操作不会改变原数组。
var arr1 = [0,2,4,6,8]; //不会改变原数组
var newArr1 = arr1.forEach(function(item,index,arr1){
// console.log(this);
// console.log(arr1);
item = item/2;
},this);
console.log(arr1);
console.log(newArr1);
使用场景:并不打算改变数据的时候,而只是想用数据做一些事情 ,比如存入数据库或则打印出来。
但是,对数据的index操作会改变原数组。
var arr1 = [0,2,4,6,8]; //会改变原数组
var newArr1 = arr1.forEach(function(item,index,arr1){
console.log(this);
console.log(arr1);
arr1[index] = item/2;
},this);
console.log(arr1);
console.log(newArr1);
和对数组里面的每一个对象的属性进行改变
var arr = [{id:1},{id:2},{id:4},{id:6},{id:8}]; //会改变原数组
var newArr = arr.forEach(function(item,index,arr){
// console.log(this);
// console.log(arr);
item.id=99999
return arr;
},this);
console.log(arr);//[{id: 99999},{id: 99999},{id: 99999},{id: 99999}]
console.log(newArr);
二、map
map()不会改变原数组的值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值;(对数据的item操作不会改变原数组)
var arr = [0,2,4,6,8]; //不会改变原数组
var newArr = arr.map(function(item,index,arr){
// console.log(this);
// console.log(arr);
return item/2;
},this);
console.log(arr); //[0,2,4,6,8]
console.log(newArr);
和对数组里面的每一个对象的属性进行改变
var arr = [{id:1},{id:2},{id:4},{id:6},{id:8}]; //不会改变原数组
var newArr = arr.map(function(item,index,arr){
// console.log(this);
// console.log(arr);
item = 99999
return arr;
},this);
console.log(arr);//[{id:1},{id:2},{id:4},{id:6},{id:8}]
console.log(newArr);
使用场景:map()适用于你要改变数据值的时候。不仅仅在于它更快,而且返回一个新的数组。
对数据的index操作会改变原数组
var arr = [{id:1},{id:2},{id:4},{id:6},{id:8}]; //会改变原数组
var newArr = arr.map(function(item,index,arr){
// console.log(this);
// console.log(arr);
item.id=99999
return arr;
},this);
console.log(arr);//[{id: 99999},{id: 99999},{id: 99999},{id: 99999}]
console.log(newArr);
map不可以跳出循环
forEach这种写法的问题在于,无法中途跳出forEach循环,break命令或return命令都不能奏效
myArray.forEach(function (value) {
console.log(value);
});
forEach可以跳出本次循环和终止循环的写法用https://blog.csdn.net/daoxiaofei/article/details/108690589
27.splice()和slice()、map()和forEach()、 filter()、reduce()的区别
1.slice(start,end):方法可以从已有数组中返回选定的元素,返回一个新数组,
包含从start到end(不包含该元素)的数组方法
注意:该方法不会更新原数组,而是返回一个子数组
2.splice():该方法想或者从数组中添加或删除项目,返回被删除的项目。(该方法会改变原数组)
splice(index, howmany,item1,...itemx)
·index参数:必须,整数规定添加或删除的位置,使用负数,从数组尾部规定位置
·howmany参数:必须,要删除的数量,
·item1..itemx:可选,向数组添加新项目
3.map():会返回一个全新的数组。使用于改变数据值的时候。会分配内存存储空间数组并返回,forEach()不会返回数据
4.forEach(): 不会返回任何有价值的东西,并且不打算改变数据,单纯的只是想用数据做一些事情,他允许callback更改原始数组的元素
5.reduce(): 方法接收一个函数作为累加器,数组中的每一个值(从左到右)开始缩减,最终计算一个值,不会改变原数组的值
6.filter(): 方法创建一个新数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。它里面通过function去做处理
28.Map数据结构 和WeakMap区别*******
– -- Set,WeakSet,Map,WeakMap的区别:
js中Map数据结构详细点
1、JavaScript的对象(Object),本质上是键值对的集合(Hash结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
2、Map数据结构类似于对象,也是键值对的集合,但是‘键’的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了"字符串—值"的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现
3、任何具有Iterator接口、且每个成员都是一个双元素的数组的数据结构都可以当作duoo次Map构造函数的参数。这就是说,Set和Map都可以
用来生成新的Map。
4、如果对同一个键多次赋值,后面的值将覆盖前面的值。
5、只有对同一个对象的引用,Map结构才将其视为同一个键。
6、Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题
7、如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map将其视为一个键
方法
map.size // 属性返回 Map 结构的成员总数。
Map.prototype.set(key, value) // 方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
// set方法返回的是当前的Map对象,因此可以采用链式写法。
Map.prototype.get(key) // 读取key对应的键值,如果找不到key,返回undefined。
Map.prototype.has(key) // 返回一个布尔值,表示某个键是否在当前 Map 对象之中
Map.prototype.delete(key) // 删除某个键,返回true。如果删除失败,返回false。
Map.prototype.clear() // 清除所有成员,没有返回值
遍历
**Map 的遍历顺序就是插入顺序**
Map.prototype.keys():// 返回键名的遍历器。
Map.prototype.values():// 返回键值的遍历器。
Map.prototype.entries():// 返回所有成员的遍历器。
Map.prototype.forEach():// 遍历 Map 的所有成员
forEach // 可以接受第二个参数,用来绑定this。
转换
- Map 转为数组 => 使用扩展运算符
- 数组 转为 Map => 数组传入 Map 构造函数
- Map 转为对象 =>如果所有 Map
的键都是字符串,它可以无损地转为对象,如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。 - 对象转为 Map => 对象转为 Map 可以通过Object.entries()。
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}
- Map 转为 JSON,Map 的键名都是字符串,这时可以选择转为对象 JSON,Map 的键名有非字符串,这时可以选择转为数组
JSON。 - JSON 转为 Map,JSON 转为 Map,正常情况下,所有键名都是字符串。整个 JSON
就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON
的逆操作。
关于WeakMap
WeakMap结构与Map结构类似,也是用于生成键值对的集合
WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
WeakMap的键名所指向的对象,不计入垃圾回收机制
它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内
Set,WeakSet,Map,WeakMap的区别 参考文档更容易理解
1.Set
成员唯一、无序且不重复;[value,value],键值与键名是一致的(或者说只有键值,没有键名);可以遍历。操作方法有:add(),delete(),has(),clear()遍历方法有:keys(),values(),entries(),forEach()
2.WeakSet
成员都是对象;成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏;不能遍历,方法有add(),delete(),has(),不能遍历;WeakSet 没有size
属性,没有办法遍历它的成员
3.Map
本质上是键值对的集合,类似集合;可以遍历,方法很多,可以跟各种数据格式转换。
4.WeakMap
只接受对象作为键名(null除外),不接受其他类型多人值作为键名;键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾机制回收,此时键名是无效的;不能遍历,方法有get、set、has、delete。
ps:因为浏览器回收机制有个计数回收发,当变量被引用的时候次数会加一,当次数为0的时候会被浏览器回收机制清除。弱引用不会考虑是否别引用的情况,作用区域结束后会之间被清除,不会导致内容泄露的情况。
29.为什么说函数是第一等公民?
参考文档
JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。
由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。
function add(x, y) {
return x + y;
}
// 将函数赋值给一个变量
var operator = add;
// 将函数作为参数和返回值
function a(op){
return op;
}
a(add)(1, 1)
// 2
30.同源策略中的CORS通信
阅读文档更容易理解
CORS 是一个 W3C 标准,全称是“跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨域的服务器,发出XMLHttpRequest请求,从而克服了 AJAX 只能同源使用的限制。
CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
31.原型链和继承(js的第一题)*****
原型链: javascript规定所有对象都有自己的原型对象(prototype)。一方面,任意一个对象都可以充当其他对象的原型;另一方面由于原型对象也是对象,所以它也有自己的原型。因此会形成一个原型链(prototype chain):对象到原型,再到原型的原型…(一层层上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说所有对象都继承了Object.prototype属性。这就是所有对象都有valueOf和toString的原因,因为这是从Object.prototype继承的。Object.prototype的原型是null,null没有任何属性和方法,也没有自己的原型,因此原型链的尽头就是null)
proto: 对象都有私有属性__proto__,从对象指向实例原型
prototype: javascript规定,每个函数都有一个prototype属性指向一个对象,对于普通函数来说,该属性基本无用;对于构造函数来说,生成实例的时候该属性自动成为实例对象的原型
constructor: prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数,由于constructor属性定义在prototype对象上面,意味着可以被其实例对象继承
- prototype指向的原型对象又有一个属性constructor,这个属性也是一个指针,指回原构造函数,即这个方法。
- __proto__和constructor属性是对象所独有的,prototype属性是函数所独有的。
- JS中函数也是一种对象,所以函数也拥有__proto__和constructor属性
- 为什么有prototype,如果没有,每次新建对象都会继承构造函数的所有属性,方法,浪费性能,用了prototype后,属性每个对象的值相同性不高,写在构造函数里,而方法通常是通用的,使用prototype可以让每个对象共享同一个方法,而不用每次都copy一个,而且能实时更新(为什么使用prototype?)
32.什么是构造函数?构造函数的特点
专门僧城市里对象的函数,他就是对象的模板,描述实例对象的结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构
构造函数的特点:
①函数是内部使用this关键字,代表了所要生成的对象实例
②生成对象的时候,必须使用new命令
33.new命令的作用和使用new命令时,它后面的函数一次执行哪些步骤?
**new命令的作用:**就是执行构造函数,返回一个实例对象
使用new命令时,它后面的函数依次执行的步骤:
①创建一个空对象,作为将要返回的对象实例
②将空对象的原型,指向构造函数的prototype属性
③将这个空对象赋值给函数内部的this关键字
④开始执行构造函数内部代码
34.this的指向******
this的执行是动态的
- 全局作用域下的this指向window
- 如果给元素的事件行为绑定函数,那么函数中的this指向当前被绑定的那个元素
- 函数中的this,要看函数执行前有没有 . , 有 . 的话,点前面是谁,this就指向谁,如果没有点,指向window
- 自执行函数中的this永远指向window
- 定时器中函数的this指向window
- 构造函数中的this指向当前的实例
- call、apply、bind可以改变函数的this指向
- 箭头函数中没有this,如果输出this,就会输出箭头函数定义时所在的作用域中的this
35.call/apply/bind的区别*****
javascript提供了call,apply,bind三种来切换、固定this的指向
call方法的第一个参数是对象(如果参数是空,null和undefined,则默认传入全局对象),就是this指向的那个对象,后面的参数则是函数调用时所需要的参数
apply方法的第一个参数也是this所要指向的那个对象,如果设为null或undefined,则等同于指定全局对象。第二个参数则是一个数组,该数组的所有成员依次作为参数,传入原函数。原函数的参数,在call方法中必须一个个添加,但是在apply方法中,必须以数组形式添加。func.apply(thisValue,[arg1,arg2,…])
bind方法会返回执行上下文被改变的函数而不会立即执行,而前两者是 直接执行该函数。他的参数和call()相同。
36.Object.create()和new object()和{}的区别?
Object.create(): Object.create()方法,接收一个对象作为参数,然后以它为原型,返回一个实例对象,该实例完全继承原型对象的属性,第二个参数是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符,可选。使用Object.create()是将对象继承到原型链上,然后可以通过对象实例的__proto__属性进行访问原型链上的属性
new Object(): new Object()创建一个实例,该实例的模板是构造函数Object,该实例的constructor是构造函数,构造函数的prototype是实例原型,实例的__proto__是实例原型
{}: {}是javascript对象字面量创建的形式,其本质和new Object()并无区别,默认都是继承了Object对象上的prototype
37.数组的常用操作
- concat() 连接两个或更多的数组,并返回结果。
- join() 把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。
- pop() 删除并返回数组的最后一个元素
- push() 向数组的末尾添加一个或更多元素,并返回新的长度。
- reverse() 颠倒数组中元素的顺序。
- shift() 删除并返回数组的第一个元素
- slice() 从某个已有的数组返回选定的元素
- sort() 对数组的元素进行排序
- splice() 删除元素,并向数组添加新元素。
- toSource() 返回该对象的源代码。
- toString() 把数组转换为字符串,并返回结果。
- toLocaleString() 把数组转换为本地数组,并返回结果。
- unshift() 向数组的开头添加一个或更多元素,并返回新的长度。
- valueOf() 返回数组对象的原始值。
38.函数的声明方式
javascript有三种声明函数的方式
- function命令:function命令声明的代码区块,就是一个函数。function命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。
function print(s) {
console.log(s);
}
上面的代码命名了一个print函数,以后使用print()这种形式,就可以调用相应的代码。这叫做函数的声明(Function Declaration)。
- 函数表达式:除了用function命令声明函数,还可以采用变量赋值的写法
var print = function(s) {
console.log(s);
};
这种写法将一个匿名函数赋值给变量。这时,这个匿名函数又称函数表达式(Function Expression),因为赋值语句的等号右侧只能放表达式。
- Function构造函数
var add = new Function(
'x',
'y',
'return x + y'
);
// 等同于
function add(x, y) {
return x + y;
}
上面代码中,Function构造函数接受三个参数,除了最后一个参数是add函数的“函数体”,其他参数都是add函数的参数。
你可以传递任意数量的参数给Function构造函数,只有最后一个参数会被当做函数体,如果只有一个参数,该参数就是函数体。
var foo = new Function(
'return "hello world";'
);
// 等同于
function foo() {
return 'hello world';
}
Function构造函数可以不使用new命令,返回结果完全一样。
总的来说,这种声明函数的方式非常不直观,几乎无人使用。
39.深克隆和浅克隆以及如何实现深克隆和浅克隆
JS深度克隆代码实现
**浅克隆:**只是拷贝了基本类型的数据,而引用类型的数据,复制后也会发生引用,我们把这种拷贝叫做‘(浅复制)浅拷贝’,换句话说,浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。
深克隆: 创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。 JSON.parse、JSON.stringify()
顾名思义,克隆就是将a同样复制一份给b
浅克隆
原始数据类型
var a = 1;
var b = a;
a = 10;
console.log(b); // 1
var a = 'hello';
var b = a;
a = 'world';
console.log(b); // hello
var a = true;
var b = a;
a = false;
console.log(b); // true
复制完之后原变量的值,依然可以赋值不影响拷贝文件
引用数据类型
var a = [0, 1, 2, 3];
var b = a;
a.push(4);
console.log(b); // [0, 1, 2, 3, 4]
由于引用类型数据存储在应用地址内存中,因此赋值复制的也是这一块地址,因此相当于两个数组对象引用了一块数据地址,所以对a或者b的任何操作或者改变都会体现在对象中。
为了避免这种情况,引入了深度克隆。
深克隆
function clone(obj) {
var o = obj instanceof Array ? [] : {};
for(var k in obj) //[1,2,3] [4,5,6,7]
o[k] = typeof obj[k] === Object ? clone(obj[k]) : obj[k];
return o;
}
var a = [[1, 2, 3], [4, 5, 6, 7]];
var b = clone(a);
a= [[1,2,3,4,5]]
console.log(a); //[[1, 2, 3], [4, 5, 6, 7]]
console.log(b); //[[1,2,3,4,5]]
深拷贝浅拷贝的做法详解
实现深拷贝:
一:层级拷贝,用递归实现(上述代码);
二:JSON解析 var b = JSON.parse(JSON.stringify(a));
40.‘’ 和‘=’ 区别是什么
=赋值
== 返回一个布尔值;相等返回true,不相等返回false;允许不同数据类型之间的比较;如果是不同类型的数据进行,会进行数据之间的转换;如果是对象数据类型的对比,比较的空间地址
“===”只要数据类型不一样就返回false
41.常见的设计模式有哪些?******
—观察者模式的原理*****
javaScript设计模式之观察者模式
观察者模式又叫做发布—订阅模式,是我们最常用的设计模式之一。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知和更新。观察者模式提供了一个订阅模型,其中对象订阅事件并在发生时得到通知,这种模式是事件驱动的编程基石,它有利益于良好的面向对象的设计。
- js工厂模式
- js构造函数模式
- js原型模式
- 构造函数 + 原型js混合模式
- 构造函数+原型的动态原型模式
- 观察者模式
- 发布订阅模式
设计模式可以分为三大类:
结构型模式(Structural Patterns): 通过识别系统中组件间的简单关系来简化系统的设计。
创建型模式(Creational Patterns): 处理对象的创建,根据实际情况使用合适的方式创建对象。常规的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。
行为型模式(Behavioral Patterns):用于识别对象之间常见的交互模式并加以实现,如此,增加了这些交互的灵活性。
前端需要了解的设计模式(10种)
创建模式:
1、工厂模式
2、单例模式
结构模式
1、装饰器模式
2、适配器模式
3、代理模式
行为型模式
1、策略模式
2、观察者模式
3、迭代器模式
4、状态模式
42.浏览器渲染的主要流程是什么?***********
将html代码按照深度优先遍历来生成DOM树。
css文件下载完后也会进行渲染,生成相应的CSSOM。
当所有的css文件下载完且所有的CSSOM构建结束后,就会和DOM一起生成Render Tree。
接下来,浏览器就会进入Layout环节,将所有的节点位置计算出来。
最后,通过Painting环节将所有的节点内容呈现到屏幕上。
43.从输入url地址到页面相应都发生了什么?
1、浏览器的地址栏输入URL并按下回车。
2、浏览器查找当前URL是否存在缓存,并比较缓存是否过期。
3、DNS解析URL对应的IP。
4、根据IP建立TCP连接(三次握手)。
5、HTTP发起请求。
6、服务器处理请求,浏览器接收HTTP响应。
7、渲染页面,构建DOM树。
8、关闭TCP连接(四次挥手)
44.TCP的三次握手和四次挥手
三次握手:
- 第一次握手:客户端发送一个SYN码给服务器,要求建立数据连接
- 第二次握手:服务器SYN和自己处理一个SYN(标志);叫SYN+ACK(确认包);发送给客户端,可以建立连接
- 第三次握手: 客户端再次发送ACK向服务器,服务器验证ACK没有问题,则建立起连接;
四次挥手:
- 第一次挥手: 客户端发送FIN(结束)报文,通知服务器数据已经传输完毕;
- 第二次挥手: 服务器接收到之后,通知客户端我收到了SYN,发送ACK(确认)给客户端,数据还没有传输完成
- 第三次挥手: 服务器已经传输完毕,再次发送FIN通知客户端,数据已经传输完毕
- 第四次挥手: 客户端再次发送ACK,进入TIME_WAIT状态;服务器和客户端关闭连接;
45.为什么建立连接是三次握手,而断开连接是四次挥手呢?
建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
46.js中跨域方法
同源协议(协议 + 端口号 + 域名要相同)
-
jsonp跨域(只能解决get)原理:动态创建script标签。利用script标签的src属性不受同源策略的限制,因为所有的src和href属性都不受同源策略的限制,可以请求到第三方服务器资源内容
步骤:1.创建一个script标签;2.script标签的src属性设置接口地址;3.接口参数,必须要带一个自定义函数名,要不然后端无法返回数据;4.通过定义函数名去接受返回的数据
-
document.domian基础域名相同 子域名不同
-
window.nam利用在一个浏览器窗口内,载入所有的域名都是共享一个window.name
-
服务器设置对CORS的支持 原理:服务器设置Access-Control-Allow-Origin HTTP响应头之后,浏览器将会允许跨域请求
-
利用h5新特性window.postMessage()
47.Ajax的四个步骤
- 创建Ajax实例
- 执行open确定要访问的连接,以及同步异步
- 监听请求状态
- 发送请求
48.ajax中get和post请求的区别
- get 一般用于获取数据
- get请求如果需要传递参数,那么会默认将参数拼接到url的后面;然后发送给服务器;
- get请求传递参数大小是有限制的;是浏览器的地址栏有大小限制;
- get安全性较低
- get 一般会走缓存,为了防止走缓存,给url后面每次拼的参数不同;放在?后面,一般用个时间戳
- post 一般用于发送数据
- post传递参数,需要把参数放进请求体中,发送给服务器;
- post请求参数放进了请求体中,对大小没有要求;
- post安全性比较高;
- post请求不会走缓存;
49.ajax的状态码******
2开头
- 200 : 代表请求成功;
3开头
- 301 : 永久重定向;
- 302: 临时转移
- 304 : 读取缓存 [表示浏览器端有缓存,并且服务端未更新,不再向服务端请求资源]
- 307:临时重定向
以4开头的都是客户端的问题;
- 400 :数据/格式错误
- 401: 权限不够;(身份不合格,访问网站的时候,登录和不登录是不一样的)
- 404 : 路径错误,找不到文件
以5开头都是服务端的问题
- 500 : 服务器的问题
- 503: 超负荷;
50.数组去重的方法
ES6的set对象 先将原数组排序,在与相邻的进行比较,如果不同则存入新数组
function unique(arr){
var arr2 = arr.sort();
var res = [arr2[0]];
for(var i=1;i<arr2.length;i++){
if(arr2[i] !== res[res.length-1]){
res.push(arr2[i]);
}
}
return res;
}
利用下标查询
function unique(arr){
var newArr = [arr[0]];
for(var i=1;i<arr.length;i++){
if(newArr.indexOf(arr[i]) == -1){
newArr.push(arr[i]);
}
}
return newArr;
}
51.JS中同步和异步,以及js的事件流
同步:在同一时间内做一件事情
异步:在同一时间内做多个事情 JS是单线程的,每次只能做一件事情,JS运行在浏览器中,浏览器是多线程的,可以在同一时间执行多个任务
52.JS中常见的异步任务
定时器、ajax、事件绑定、回调函数、async await、promise
53.异步操作的模式
参考链接更容易理解,具体过程也可以参考
两个函数f1和f2,f2必须在f1执行完成之后执行
下面有几个异步操作的几个模式
1.**回调函数模式:**回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(coupling),使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。
2.**事件监听模式:**这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以“去耦合”(decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。
3.**发布订阅模式又称之为观察者模式:**这种方法的性质与“事件监听”类似,但是明显优于后者。因为可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
54.异步操作的流程控制*(如果有多个异步操作,就存在一个流程控制的问题:如何确定异步操作执行的顺序,以及如何保证遵守这种顺序。如果有六个这样的异步任务,需要全部完成后,才能执行最后的final函数。请问应该如何安排操作流程?*)(a,b,c三个异步任务,保证a,b完成之后,再执行c任务或者更多异步任务执行完成之后再执行最后一个任务)
1.串行执行: 我们可以编写一个流程控制函数,让它来控制异步任务,一个任务完成以后,再执行另一个。这就叫串行执行。
2.并行执行: 流程控制函数也可以是并行执行,即所有异步任务同时执行,等到全部完成以后,才执行final函数。
相比而言,并行的写法只要一秒,就能完成整个脚本。这就是说,并行执行的效率较高,比起串行执行一次只能执行一个任务,较为节约时间。但是问题在于如果并行的任务较多,很容易耗尽系统资源,拖慢运行速度。因此有了第三种流程控制方式。
3.并行和串行结合: 所谓并行与串行的结合,就是设置一个门槛,每次最多只能并行执行n个异步任务,这样就避免了过分占用系统资源。
55.Promise处理异步******
他是ES6中新增加的一个类(new Promise),目的是为了管理JS中的异步编程的,所以把他称为“Promise设计模式”
new Promise 经历三个状态:padding(准备状态:初始化成功、开始执行异步的任务)、fullfilled(成功状态)、rejected(失败状态)==
Promise本身是同步编程的,他可以管理异步操作的(重点),new Promise的时候,会把传递的函数立即执行
Promise函数天生有两个参数,resolve(当异步操作执行成功,执行resolve方法),rejected(当异步操作失败,执行reject方法)
then()方法中有两个函数,第一个传递的函数是resolve,第二个传递的函数是reject
ajax中false代表同步,true代表异步,如果使用异步,不等ajax彻底完成
56.async await函数
- async/await函数是异步代码的新方式
- async/await是基于promise实现的
- async/await使异步代码更像同步代码
- await 只能在async函数中使用,不能再普通函数中使用,要成对出现
- 默认返回一个promise实例,不能被改变
- await下面的代码是异步,后面的代码是同步的
57.异步回调(如何解决回调地狱)
promise、generator、async/await
promise: 1.是一个对象,用来传递异步操作的信息。代表着某个未来才会知道结果的时间,并未这个事件提供统一的api,供进异步处理
2.有了这个对象,就可以让异步操作以同步的操作的流程来表达出来,避免层层嵌套的回调地狱
3.promise代表一个异步状态,有三个状态pending(进行中),Resolve(以完成),Reject(失败)
4.一旦状态改变,就不会在变。任何时候都可以得到结果。从进行中变为以完成或者失败
promise.all() 里面状态都改变,那就会输出,得到一个数组
promise.race() 里面只有一个状态变为rejected或者fulfilled即输出
promis.finally()不管指定不管Promise对象最后状态如何,都会执行的操作(本质上还是then方法的特例)
58.前端事件流
事件流描述的是从页面中接受事件的顺序,事件 捕获阶段 处于目标阶段 事件冒泡阶段 addeventListener 最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
1、事件捕获阶段:实际目标div在捕获阶段不会接受事件,也就是在捕获阶段,事件从document到<html>再到<body>就停止了。
2、处于目标阶段:事件在div发生并处理,但是事件处理会被看成是冒泡阶段的一部分。
3、冒泡阶段:事件又传播回文档
阻止冒泡事件event.stopPropagation()
function stopBubble(e) {
if (e && e.stopPropagation) { // 如果提供了事件对象event 这说明不是IE浏览器
e.stopPropagation()
} else {
window.event.cancelBubble = true //IE方式阻止冒泡
}
}
阻止默认行为event.preventDefault()
function stopDefault(e) {
if (e && e.preventDefault) {
e.preventDefault()
} else {
// IE浏览器阻止函数器默认动作的行为
window.event.returnValue = false
}
}
59.事件如何先捕获后冒泡?
在DOM标准事件模型中,是先捕获后冒泡。但是如果要实现先冒泡后捕获的效果, 对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后再执行捕获事件。
哪些事件不支持冒泡事件:鼠标事件:mouserleave mouseenter 焦点事件:blur focus UI事件:scroll resize
60.空数组的布尔值是true(JavaScript中的空数组[ ]和空对象{ }的布尔值是什么?)
JavaScript中的空数组[ ]和空对象{ }的布尔值是什么?
–javascript中在判断条件中为false的几种值******
javascript中在判断条件中为false的几种值:
Number类型:0 NaN
String类型:""
Boolean类型:false
Undefined类型:undefined
Null类型:null
61.判断一个对象是否为数组**********
- 验证原型对象(Array.prototype.isPrototypeOf(obj))
- 用构造函数来验证(obj instanceof Array)
- 根据对象的class属性(类属性),跨原型链调用toString()方法。(Object.prototype.toString )(Object.prototype.toString.call())(可以使用Function.call()的方法,其中call可以这么理解,相当于obj去借用这个 Object.prototype.toString()😉
- Array.isArray()方法。(ES5中的新增方法)
62.达到iteartor接口的条件?*********
原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
服务器端和数据结构和算法
http缓存****(优先使用优先使用Etage,判断缓存是否过期)*********
http缓存
http缓存:强缓存,协商缓存
浏览器缓存:本地缓存,默认缓存
http缓存详解
1、为什么要缓存
相对于CPU的计算和页面加载速度(mm级)来说,网络请求更耗时,所以通过http缓存的方式时间来减少网络请求,从而能达到访问前端页面的性能优化。
http缓存策略分为强制缓存和协商缓存
2、强制缓存 Catch-Control
Cache-Control 在Response Headers中,控制强制缓存的逻辑.
(例如 Cache-Control:max-age=31536000(秒)=1年).
Expires 在Response Headers中,设置缓存过期,已被Catch-Control代替
- Cache-control的值
- max-age 设置缓存的最大过期时间 **
- no-catch 有本地缓存,不用强制缓存,向服务端请求 **
- no-store 不让服务端做缓存,完全不缓存
- private 发起请求的浏览器才能使用返回数据的缓存
- public 这个HTTP请求它返回的内容所经过的任何路径中,包括中间的一些HTTP
代理服务器以及发出请求的客户端浏览器,都可以进行对返回内容的缓存操作
- http强制缓存的过程
浏览器初次请求时,服务端返回资源和Catch-Control,
再次请求时,若Cache-control中响应头信息中设置了max-age=***[秒],则访问本地缓存。
- http协商缓存的过程
服务端缓存策略-服务端判断资源是否用缓存.
服务端判断客户端资源标识是否跟服务端资源一样,一致则返回 304,负责返回 200和最新资源
在Response Headers中,资源标识分为两种:
Last-Modified 资源最后被修改的时间
Etag 资源的唯一标识,Etag值根据资源内容改变而改变
- Last-Modified为资源标识协商缓存的过程
第一次请求,服务器端的返回状态200、资源,
同时有 Last-Modified 的属性标记此文件在服务器端最后被修改的时间。
第二次请求,根据HTTP协议的规定,浏览器会向服务器传送If-*Modified-Since报头,询问该时间之后文件是否有被修改过:
如果服务器端的资源没有变化,则自动返回 HTTP 304(Not Changed.)状态码,内容为空,这样就节省了传输数据量。
当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。
以Etag为资源标识协商缓存的过程
第一次请求,服务器返回状态码200、资源和一堆Header,
包括Etag(例如"2e681a-6-5d044840")属性标识唯一资源
第二次请求,客户端发送一个If-None-Match头,这个头的内容就是第一次请求时服务器返回的Etag:2e681a-6-5d044840
服务器判断发送过来的Etag和计算出来的Etag匹配
If-None-Match为False资源没有变化,返回304,客户端继续使用本地缓存
优先使用Etage
Last-Modified只能精确到秒级
如果资源被重复生成,而内容不变,则Etag更精确
Etag要优于Last-Modified。Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;
下图包括协商缓存和强缓存
如何判断缓存是否过期
缓存是否过期主要与 Response Header 的两类头部有关,一个是缓存最大有效时间(记为 freshness_lifetime),另一个是缓存已经存在的时间(记为 current_age)。
有个上面两个值后,就可以判断缓存是否过期了,逻辑如下:
if (freshness_lifetime > current_age) {
未过期
} else {
已过期
}
刷新操作方式,对缓存的影响
优化
1.前端有哪些页面优化的方法************
- 减少 HTTP请求数
- 从设计实现层面简化页面
- 合理设置 HTTP缓存
- 在js中尽量减少闭包的使用
- 在JS中避免“嵌套循环”和 “死循环”
- 尽可能使用事件委托(事件代理)来处理事件绑定的操作
- 尽量合并css和js文件
- 将外部脚本置底(将脚本内容在页面信息内容加载后再加载)
- 减少对DOM的操作
- 多图片网页使用图片懒加载。
- 资源合并与压缩
- 合并 CSS图片,减少请求数的又一个好办法。
- 尽量使用字体图标或者SVG图标,来代替传统的PNG等格式的图片
1.减少HTTP请求
减少HTTP请求的方式很多,常见的包括CSS Sprites、合并JS和CSS、图片地图等。遵守这条规则可以改善首次访问网站的响应时间。
2.使用CDN(内容发布网络)
只有10%-20%的最终用户响应时间花在了下载HTML文档上,其余的80%-90%时间花在了下载页面中的所有组件上。如果web服务器离服务器更近,则一个HTTP请求的响应时间将缩短。CDN是一组分布在多个不同地理位置的web服务器,每个服务器都拥有所有网站的文件副本,用户访问网站时,就可以从离用户最近的服务器发送所需的文件给客户端。Yahoo!Shopping网站使用CDN后响应时间减少了20%。
3.添加Expires头
通过使用Expires头,浏览器可以在用户首次访问网站后,将页面的资源缓存下来。可以为Expires指定过期时间,在指定时间后,缓存将失效。
web服务器使用Expires/头来告诉web客户端它可以使用一个组件的当前副本,直到指定时间为止
4.启用Gzip
客户端可以通过HTTP请求中的Accept-Encoding头来标识对压缩的支持(Accept-Encoding:gzip,deflate),服务器看到请求中有这个头,就会使用客户端列出的一种方法来压缩响应。大多数网站使用gzip压缩了HTML。
5.将CSS放在顶部
将CSS放在底部会发生无样式闪烁,浏览器会先加载HTML,然后加载CSS,会出现一段没有任何样式的“白屏时间”。而将CSS放在顶部则能规避这个问题。
6.将脚本放在底部
将脚本放在顶部或页面中,浏览器会对script标签内的内容进行解析,从而阻塞样式的渲染。除此之外,HTTP1.1规范建议浏览器从每个主机并行的下载两个组件,在高版本的IE和chrome、Firefox等浏览器则支持并行下载六个组件。但script会阻塞并行下载。因此我们应该将脚本放在底部。
7.避免CSS表达式
CSS表达式具备求值计算能力,然而每次页面发生重绘时,CSS表达式会影响页面的加载时间。
8.使用外部JavaScript和CSS
使用大量内联的JS和CSS就不能实现样式、结构、行为的分离,这样会在维护上造成很多麻烦,同时也会增大网页的体积。
2.webpack相关面试题 *************
3.什么是重绘和回流(重排)?怎样减少重绘和回流的操作?*****
重绘和回流
什么是回流,什么是重绘,有什么区别?
重绘与回流(重排)
回流(reflow): 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。
重绘(repaint): 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。
每个页面至少需要一次回流+重绘。
回流必将引起重绘
回流什么时候发生?
1、添加或者删除可见的DOM元素;
2、元素位置改变;
3、元素尺寸改变——边距、填充、边框、宽度和高度
4、内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;
5、页面渲染初始化;
6、浏览器窗口尺寸改变——resize事件发生时;
var s = document.body.style;
s.padding = "2px"; // 回流+重绘
s.border = "1px solid red"; // 再一次 回流+重绘
s.color = "blue"; // 再一次重绘
s.backgroundColor = "#ccc"; // 再一次 重绘
s.fontSize = "14px"; // 再一次 回流+重绘
// 添加node,再一次 回流+重绘
document.body.appendChild(document.createTextNode('abc!'));
如何性能优化? 尽量减少重绘与回流的次数
直接使用className修改样式,少用style设置样式
让要操作的元素进行”离线处理”,处理完后一起更新
使用DocumentFragment进行缓存操作,引发一次回流和重绘
使用display:none技术,只引发两次回流和重绘;
将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素为动画的 HTML 元素,例如动画,那么修改他们的 CSS 是会大大减小 reflow .
完成功能是前提,在完成功能的情况下想着优化代码
var pNode,fragment = document.createDocumentFragment();
//动态创建20个p标签,先用DocumentFragment 对象来缓存
for(var i=0; i<20; i++){
pNode = document.createElement('p');
pNode.innerHTML = i;
fragment.appendChild(pNode);
}
document.body.appendChild(fragment);
还有添加节点的时候比如要添加一个div里面有三个子元素p,如果添加div再在里面添加三次p,这样就触发很多次回流和重绘,我们可以用cloneNode(true or false) 来避免,一次把要添加的都克隆好再appened就好了,还有其他很多的方法就不一一说了
4.css阻塞和js阻塞*******
CSS 阻塞 和 js 阻塞详解
1、css加载不会阻塞DOM树得解析
2、css加载会阻塞DOM树的渲染
3、css加载会阻塞后面js语句的执行
因此,为了避免让用户看到长时间的白屏时间,我们应该尽可能提高css加载速度,比如可以使用以下几种方法:
1、使用CDN(因为CDN会根据你的网络状况,替你挑选最近的一个具有缓存内容的节点为你提供资源,因此可以减少加载时间)
2、对css进行压缩(可以用很多打包工具,比如webpack,gulp等,也可以通过开启gzip压缩)
3、合理的使用缓存(设置cache-control,expires,以及E-tag都是不错的,不过bing要注意一个问题,就是文件更新后,你要避免缓存而带来的影响。其中一个解决防范是在文件名字后面加一个版本号)
4、减少http请求数,将多个css文件合并,或者是干脆直接写成内联样式(内联样式的一个缺点就是不能缓存
css与html
1.垂直居中的方法**********
div水平垂直居中的六种方法
css实现div水平垂直居中
①利用绝对定位position: absolute;和transform: translate(-50%, -50%)在不知道div的宽高时移动宽高的50%
②利用绝对定位position: absolute;和margin-left,margin-top移动div的宽高一般
③利用绝对定位position: absolute;和margin:auto;left,top,bottom,right都设置为0
④flex布局(父元素设置flex布局,align-item:center先设置子元素在父元素中垂直居中,justify-content:center设置子元素在父元素中居中)
⑤table-cell实现水平垂直居中:table-cell middle center组合使用(display: table-cell;vertical-align: middle;text-align: center;)
⑥绝对定位:calc() 函数动态计算实现水平垂直居中
①
div{
background:red;
position: absolute;
left:50%;
top:50%;
border: 1px solid red;
transform: translate(-50%, -50%);
}
②
div{
width:400px;
height: 400px;
border: 1px solid red;
position: absolute;
left:50%;
top:50%;
margin-left:-200px;
margin-top:-200px;
}
③
div{
width: 600px;
height: 600px;
background: red;
position:absolute;
left:0;
top: 0;
bottom: 0;
right: 0;
margin: auto;
}
④
.box{
height:800px;
-webkit-display:flex;
display:flex;
-webkit-align-items:center;
align-items:center;
-webkit-justify-content:center;
justify-content:center;
border:1px solid #ccc;
}
div.child{
width:600px;
height:600px;
background-color:red;
}
⑤
.table-cell {
display: table-cell;
vertical-align: middle;
text-align: center;
width: 240px;
height: 180px;
border:1px solid #666;
}
⑥
.calc{
position: relative; border: 1px solid #ccc; width: 400px; height: 160px;
}
.calc .child{
position: absolute; width: 200px; height: 50px;
left:-webkit-calc((400px - 200px)/2);
top:-webkit-calc((160px - 50px)/2);
left:-moz-calc((400px - 200px)/2);
top:-moz-calc((160px - 50px)/2);
left:calc((400px - 200px)/2);
top:calc((160px - 50px)/2);
}
(1)margin:auto法
css:
div{
width: 400px;
height: 400px;
position: relative;
border: 1px solid #465468;
}
img{
position: absolute;
margin: auto;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
html:
<div>
<img src="mm.jpg">
</div>
定位为上下左右为0,margin:0可以实现脱离文档流的居中.
(2)margin负值法
.container{
width: 500px;
height: 400px;
border: 2px solid #379;
position: relative;
}
.inner{
width: 480px;
height: 380px;
background-color: #746;
position: absolute;
top: 50%;
left: 50%;
margin-top: -190px; /*height的一半*/
margin-left: -240px; /*width的一半*/
}
补充:其实这里也可以将marin-top和margin-left负值替换成,
transform:translateX(-50%)和transform:translateY(-50%)
(3)table-cell(未脱离文档流的)
设置父元素的display:table-cell,并且vertical-align:middle,这样子元素可以实现垂直居中。
css:
div{
width: 300px;
height: 300px;
border: 3px solid #555;
display: table-cell;
vertical-align: middle;
text-align: center;
}
img{
vertical-align: middle;
}
(4)利用flex
将父元素设置为display:flex,并且设置align-items:center;justify-content:center;
css:
.container{
width: 300px;
height: 200px;
border: 3px solid #546461;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
}
.inner{
border: 3px solid #458761;
padding: 20px;
}
2.less、scss/sass的区别*
https://blog.csdn.net/guoqiankunmiss/article/details/106113821
一,less、scss/sass
less是一种动态样式语言,比css多出一些功能(变量、继承、运算、函数等),less可以在客户端运行,也可以在服务器端运行
scss/sass也是一种动态样式语言,比css多出一些功能(变量、嵌套、运算,混入(Mixin)、继承、颜色处理,函数等),方便阅读
scss和sass的区别
sass是缩排语法,不能将css代码加到sass里面,因此sass进行改良,Sass 3就变成了Scss(sassy css)。与原来的语法兼容,只是用{}取代了原来的缩进,更容易阅读。
二,less、scss/sass的区别
1、编译环境
less是需要引入less.js来处理less代码输出css到浏览器,也可以在开发环节使用less,然后编译成css文件,直接放到项目中,也有 Less.app、SimpleLess、CodeKit.app这样的工具,也有在线编译地址。在一般前端项目里面使用 yarn add less yarn add less-loader添加到对应的项目里面
sass的安装需要Ruby环境,是在服务端处理的
2、变量
1、less、scss的变量符不一样
less是@、scss是$、css变量是两根连词线(- -)
2、变量作用域不一样
/** Less-作用域*/
@color: #00c; /* 蓝色 */
#header {
@color: #c00; /* red */
border: 1px solid @color; /* 红色边框 */
}
#footer {
border: 1px solid @color; /* 蓝色边框 */
}
/** Less-作用域编译后*/
#header{border:1px solid #cc0000;}
#footer{border:1px solid #0000cc;}
/**scss-作用域*/
$color: #00c; /* 蓝色 */
#header {
$color: #c00; /* red */
border: 1px solid $color; /* 红色边框 */
}
#footer {
border: 1px solid $color; /* 蓝色边框 */
}
/** Sass-作用域编译后*/
#header{border:1px solid #c00}
#footer{border:1px solid #c00}
/** 我们可以看出来,less和scss中的变量会随着作用域的变化而不一样。
*/
3、输出
less没有输出设置
scss提供四种输出选项:nested, compact, compressed 和 expanded。
有四种选择,默认为nested
nested:嵌套缩进的css代码
expanded:展开的多行css代码
compact:简洁格式的css代码
compressed:压缩后的css代码
4、条件语句
less不支持条件语句
scss语句支持if{}else{}、for{}循环语句
/** if else */
@if lightness($color) > 30% {
/** do....*/
} @else {
/** do....*/
}
/** 循环*/
@for $i from 1 to 10 {
.border-#{$i} {
border: #{$i}px solid red;
}
}
5、引入外部css
scss引用的外部文件命名必须以_开头, 如下例所示:其中_test1.scss、_test2.scss、_test3.scss文件分别设置的h1 h2 h3。文件名如果以下划线_开头的话,sass会认为该文件是一个引用文件,不会将其编译为css文件.
// 源代码:
@import "_test1.scss";
@import "_test2.scss";
@import "_test3.scss";
// 编译后:
h1 {
font-size: 17px;
}
h2 {
font-size: 17px;
}
h3 {
font-size: 17px;
}
less引用外部文件和css中的@import没什么差异。
三、总结
sass/scss或是less,都可以看作为一种基于css之上的高级语言,其目的是使得css开发更灵活和更强大,sass的功能比less强大,基本可以说是一种真正的编程语言了,less则相对清晰明了,易于上手,对编译环境要求比较宽松,在实际开发中更倾向于选择less。但如果认真深入scss之后还是建议切换到scss,因为更加强大,更好用。
3.H5的十个新特性***************
- 语义标签
- 增强型表单
- 视频和音频
- Canvas绘图
- SVG绘图
- 地理定位
- 拖放API
- WebWorker
- WebStorage
10.WebSocket
4.flex布局
display:flex; 在父元素设置,子元素受弹性盒影响,默认排成一行,如果超出一行,按比例压缩
flex:1; 子元素设置,设置子元素如何分配父元素的空间,flex:1,子元素宽度占满整个父元素align-items:center 定义子元素在父容器中的对齐方式,center 垂直居中justify-content:center 设置子元素在父元素中居中,前提是子元素没有把父元素占满,让子元素水平居中。
5.盒子模型
标准盒子模型:宽度=内容的宽度(content)+ border + padding
低版本IE盒子模型:宽度=内容宽度(content+border+padding)
6.rem和em的区别
rem和em的使用和区别详解
rem
当使用rem单位,他们转化为像素大小取决于页根元素的字体大小,即html元素的字体大小。根元素的字体大小乘以rem值。例如,根元素字体大小16px,10rem将等同于160px,即10*16 = 160.
CSS padding设置为 10rem
计算结果为160px
em
当使用em单位时,像素值将是em值乘以使用em单位的元素的字体大小。 例如,如果一个 div 有 18px 字体大小,10em 将等同于 180px,即 10 × 18 = 180。
CSS padding设置为 10em
计算到 180px (或接近它)
7.什么是BFC?BFC的作用?
什么是BFC?详解
BFC是一个独立的布局环境,其中元素布局是不受外界的影响,并且在一个BFC中,块盒与行盒(行盒由一行中所有的内联元素所组成)都会垂直的沿着其父元素的边框排列。
BFC的布局规则
- 内部的Box会在垂直方向,一个接一个地放置。
- Box垂直方向的距离由margin决定。属于 同一个 BFC的两个相邻Box的margin会发生重叠。
- 每个盒子(块盒与行盒)的margin box的左边,与包含块border
box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。 - BFC的区域不会与float box重叠。
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
- 计算BFC的高度时,浮动元素也参与计算。
如何创建BFC
1、float的值不是none。
2、position的值不是static或者relative。
3、display的值是inline-block、table-cell、flex、table-caption或者inline-flex
4、overflow的值不是visible
BFC的作用
1、利用BFC解决margin重叠
<!DOCTYPE html>
<html lang="en">
<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>防止margin重叠</title>
</head>
<style>
*{
margin: 0;
padding: 0;
}
p {
color: #f55;
background: yellow;
width: 200px;
line-height: 100px;
text-align:center;
margin: 30px;
}
</style>
<body>
<p>看看我的 margin是多少</p>
<p>看看我的 margin是多少</p>
</body>
</html>
页面生成的效果就是这样的:
根据第二条,属于同一个BFC的两个相邻的Box会发生margin重叠,所以我们可以设置,两个不同的BFC,也就是我们可以让把第二个p用div包起来,然后激活它使其成为一个BFC
<!DOCTYPE html>
<html lang="en">
<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>防止margin重叠</title>
</head>
<style>
*{
margin: 0;
padding: 0;
}
p {
color: #f55;
background: yellow;
width: 200px;
line-height: 100px;
text-align:center;
margin: 30px;
}
div{
overflow: hidden;
}
</style>
<body>
<p>看看我的 margin是多少</p>
<div>
<p>看看我的 margin是多少</p>
</div>
</body>
</html>
2、自适应两栏布局
每个盒子的margin box的左边,与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<style>
*{
margin: 0;
padding: 0;
}
body {
width: 100%;
position: relative;
}
.left {
width: 100px;
height: 150px;
float: left;
background: rgb(139, 214, 78);
text-align: center;
line-height: 150px;
font-size: 20px;
}
.right {
height: 300px;
background: rgb(170, 54, 236);
text-align: center;
line-height: 300px;
font-size: 40px;
}
</style>
<body>
<div class="left">LEFT</div>
<div class="right">RIGHT</div>
</body>
</html>
页面:
又因为:
BFC的区域不会与float box重叠。
所以我们让right单独成为一个BFC
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<style>
*{
margin: 0;
padding: 0;
}
body {
width: 100%;
position: relative;
}
.left {
width: 100px;
height: 150px;
float: left;
background: rgb(139, 214, 78);
text-align: center;
line-height: 150px;
font-size: 20px;
}
.right {
overflow: hidden;
height: 300px;
background: rgb(170, 54, 236);
text-align: center;
line-height: 300px;
font-size: 40px;
}
</style>
<body>
<div class="left">LEFT</div>
<div class="right">RIGHT</div>
</body>
</html>
页面:
right会自动的适应宽度,这时候就形成了一个两栏自适应的布局。
3、清除浮动
当我们不给父节点设置高度,子节点设置浮动的时候,会发生高度塌陷,这个时候我们就要清楚浮动
比如这样:
<!DOCTYPE html>
<html lang="en">
<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>
<style>
.par {
border: 5px solid rgb(91, 243, 30);
width: 300px;
}
.child {
border: 5px solid rgb(233, 250, 84);
width:100px;
height: 100px;
float: left;
}
</style>
<body>
<div class="par">
<div class="child"></div>
<div class="child"></div>
</div>
</body>
</html>
页面:
这个时候我们根据最后一条:
计算BFC的高度时,浮动元素也参与计算。
给父节点激活BFC
<!DOCTYPE html>
<html lang="en">
<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>
<style>
.par {
border: 5px solid rgb(91, 243, 30);
width: 300px;
overflow: hidden;
zoom:1;
//由于在IE比较低版本的浏览器中使用overflow:hidden;是不能达到这样的效果,因此需要加上 zoom:1;所以为了让兼容性更好的话,如果需要使用overflow:hidden来清除浮动,那么最好加上zoom:1;
}
.child {
border: 5px solid rgb(233, 250, 84);
width:100px;
height: 100px;
float: left;
}
</style>
<body>
<div class="par">
<div class="child"></div>
<div class="child"></div>
</div>
</body>
</html>
页面:
总结
以上例子都体现了:
BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
因为BFC内部的元素和外部的元素绝对不会互相影响,因此, 当BFC外部存在浮动时,它不应该影响BFC内部Box的布局,BFC会通过变窄,而不与浮动有重叠。同样的,当BFC内部有浮动时,为了不影响外部元素的布局,BFC计算高度时会包括浮动的高度。避免margin重叠也是这样的一个道理。
8.关于overflow:hidden的作用
关于overflow:hidden的作用(溢出隐藏、清除浮动、解决外边距塌陷等等)
9.position的属性值有哪些?
1、absolute:
生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。
2、fixed:
生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。
3、relative:
生成相对定位的元素,相对于其正常位置进行定位。因此,“left:20” 会向元素的 LEFT 位置添加 20 像素。
4、static:
默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
10.H5新特性中语义化标签的好处*********
语义化标签,按照字面意思理解就是“具有特定意义的标签”。
好处:
1、有意义的标签使得页面结构化,即使去掉css样式页面也能以一种清晰的结构展现。
2、根据文档显示结构更易于后期的维护。
3、除了人容易理解外,程序和其他设备也可以理解有意义的标签。例如,搜索引擎可以识别出标题行,(因为它被包围在h1中)并给它分配更高的重要度。同时,屏幕阅读器的用户可以依靠标题作为辅助的页面导航。
(X)HTML中包含的一些具有语义化的标签有:
h1 h2
ul ul ol
strong em
bockquote cite
color legend label
caption thead tbody tfoot
Ajax
1.axios是什么?怎么使用?描述使用它实现登录功能的流程?
简述ajax、axios和fetch的区别
(1)请求后台资源的模块。
(2)$ npm install axios -S装好复制代码
(3)然后发送的是跨域,需在配置文件中config/index.js进行设置。后台如果是Tp5则定义一个资源路由。 js中使用import进来,然后.get或.post。返回在.then函数中如果成功,失败则是在.catch函数中
axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范,它本身具有以下特征:
1.从浏览器中创建 XMLHttpRequest
2.支持 Promise API
3.客户端支持防止CSRF
4.提供了一些并发请求的接口(重要,方便了很多的操作)
5.从 node.js 创建 http 请求
6.拦截请求和响应
7.转换请求和响应数据
8.取消请求
9.自动转换JSON数据
PS:防止CSRF:就是让你的每个请求都带一个从cookie中拿到的key, 根据浏览器同源策略,假冒的网站是拿不到你cookie中得key的,这样,后台就可以轻松辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略。
axios+promise整合http请求
项目问题和实际例子(都很重要)
1.网页上遇到的验证码解决的式什么问题,防止什么问题?
(1)有效防止这种问题对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试
(2)防止用户利用机器人自动注册、登录、灌水,都采用了验证码技术。所谓验证码,就是将一串随机产生的数字或符号,生成一幅图片,
图片里加上一些干扰象素(防止OCR),由用户肉眼识别其中的验证码信息,输入表单提交网站验证,验证成功后才能使用某项功能。
2.权限层级按钮
3.react项目中遇到的困难(发表评论时间显示)
4.用let不用var的例子(防止变量提升的时候会用到,闭包做为外层函数的时候也会用到let)
function letTest() {
var x = 1;
if (true) {
let x = 2; *// 不同的变量*
console.log(x); *// 2*
}
console.log(x); *// 1*
}
//如果都用var就会变量提升
function letTest() {
var x = 1;
if (true) {
var x = 2; *//第一个var x会被第二个var x覆盖
console.log(x); *// 2*
}
console.log(x); *// 2*
}
5.jquery链式操作如何
实现使用jquery
的链式写法,设置层级菜单的收缩(用js不能实现只能用jquery)
就打层级菜单实例(当我点击某一个子菜单,我要获取到子菜单的父级菜单,我就要.parent().attr()获取父级菜单的自定义属性传给后端,或者我要为父级添加一个class然后就addClass())这种就是链式操作
6.解决高度坍塌的方法
1、给父元素添加overflow:hidden;zoom:1;(针对IE6的兼容,可以不写)
2、子元素的平级最末尾加一个空的div;设置clear:both;
3、给坍塌的元素加上一个伪类:after伪类
:after{
content:” ”;
clear:both;
display:block;
height:0;
overflow:hidden;
visibility:hidden;
}
:after对于IE8以下有兼容问题,所以给塌陷的元素{zoom: 1;}
7.项目中哪块东西自己做的不错的(权限这块)
8.在父窗口中获取Iframe中的元素 & 在Iframe中获取父窗口中的元素
https://blog.csdn.net/fukaiit/article/details/79452361
1.在Iframe中获取父窗口中的元素
(1)js
window.parent.document.getElementById("父窗口中元素的id").事件();
(2)jQuery
$('#父窗口中元素的id', parent.document).事件();
2.在父窗口中获取Iframe中的元素
(1)js
window.frames["iframe中的name值"].document.getElementById("iframe中控件的id").事件();
(2)jQuery
// 方式1
$("#iframe的id").contents().find("#iframe中控件的id").事件();
//方式2
$("#iframe中控件的id",document.frames("iframe的name").document).事件();