私人笔记
私人笔记,仅作方便手机阅读使用
私人笔记,仅作方便手机阅读使用
私人笔记,仅作方便手机阅读使用
markdown 给文字添加颜色
-
<font color=red>我是红色</font>
=> 我是红色 -
$\color{red}{我是红色}$
=> -
使用带!的引用(预览时可能看不到效果)
这是一段带红色感叹号的引用
http请求头 Content-Type 类型
(待补充···)
常见的媒体格式类型如下:
text/html : HTML格式
text/plain :纯文本格式
text/xml : XML格式
image/gif :gif图片格式
image/jpeg :jpg图片格式
image/png:png图片格式
以application开头的媒体格式类型:
application/xhtml+xml :XHTML格式
application/xml : XML数据格式
application/atom+xml :Atom XML聚合格式
application/json : JSON数据格式
application/pdf :pdf格式
application/msword : Word文档格式
application/octet-stream : 二进制流数据(如常见的文件下载)
application/x-www-form-urlencoded : <form encType=''>中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)
复制代码
另外一种常见的媒体格式是上传文件之时使用的:
multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式
复制代码
以上就是我们在日常的开发中,经常会用到的若干content-type的内容格式。
es6新特性
常用的有:
默认参数
let、const
模板文字(${})
多行字符串(``)
for···of
解构赋值
箭头函数
Promises
延展操作符(...)
import 和 export
数据类型
最新的 ECMAScript 标准定义了 7 种数据类型:
-
6 种原始类型:
Boolean
Null
Undefined
Number
String
Symbol
(ECMAScript 6 新定义)
-
和
Object
判断数据类型 (typeof vs instanceof)
typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么?
typeof
typeof
对于原始类型来说,除了 null
都可以显示正确的类型
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
// typeof 不能判断null
typeof null // 'object'
复制代码
typeof
对于对象来说,除了函数都会显示 object
(typeof console.log // 'function'
),所以说 typeof
并不能准确判断变量到底是什么类型
instanceof
如果我们想判断一个对象的正确类型,这时候可以考虑使用 instanceof
,因为内部机制是 通过原型链来判断 的。
对于原始类型来说,你想直接通过 instanceof 来判断类型是不行的。
const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true
var str = 'hello world'
str instanceof String // false
var str1 = new String('hello world')
str1 instanceof String // true
复制代码
值类型 vs 引用类型
除了原始类型,ES 还有引用类型,typeof
识别出来的类型中,只有object
和 function
是引用类型,其他都是值类型。
根据 JavaScript
中的变量类型传递方式,又分为值类型和引用类型,值类型变量包括 Boolean
、String
、Number
、Undefined
、Null
,引用类型包括了 Object
类的所有,如 Date
、Array
、Function
等。在参数传递方式上,值类型是按值传递,引用类型是按共享传递。
Ramda 常用函数
函数 | 作用 | 示例 |
---|---|---|
R.clone() | 克隆一个对象/数组 | const a = R.clone(b) |
R.defaultTo() | 设置默认值(若参数为undefined或null则取默认值) | const defaultTo42 = R.defaultTo(42); |
R.difference() | 找出前一个数组中不包含于后一个数组中的元素 | R.difference([7,6,5,4,3], [1,2,3,4]); //=> [7,6,5] |
R.without() | 找出后一个数组中不包含于前一个数组中的元素 | R.without([7,6,5,4,3], [1,2,3,4]); //=> [1,2] |
R.dissoc() | 返回不包含prop属性的新对象。(R.omit()可以传一个数组 | R.dissoc('b', {a: 1, b: 2, c: 3}); //=> {a: 1, c: 3} |
R.omit() | 返回省略指定键的对象的部分副本。(R.dissoc()只能传一个字符串参数) | R.omit(['a', 'd'], {a: 1, b: 2, c: 3, d: 4}); //=> {b: 2, c: 3} |
R.pick() | 返回仅包含指定键的对象的部分副本。不存在则返回空对象{} | R.pick(['a', 'd'], {a: 1, b: 2, c: 3, d: 4}); //=> {a: 1, d: 4} |
R.pickBy() | 返回仅包含满足所提供谓词的键的对象的部分副本。 | const isUpperCase = (val, key) => key.toUpperCase() === key; R.pickBy(isUpperCase, {a: 1, b: 2, A: 3, B: 4}); //=> {A: 3, B: 4} |
R.equals() | 判断是否相等 | const isEquals = R.equals({info:[1,{2}]},{info:[1,{3}]}); //=>false |
R.flatten() | 扁平化数组 | const fla = R.flatten([1,[2,3,[4,5,[6,[7,[8],[9],[10]]]]]]); //=>[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
R.fromPairs() | 从列表键值对创建新对象。如果一个键出现在多个对中,则最右边的对包含在对象中。 | R.fromPairs([['a', 1], ['b', 2], ['c', 3]]); //=> {a: 1, b: 2, c: 3} |
R.isEmpty() | 判断目标是否为空(数组/对象/字符串) | const isEmptyObj = R.isEmpty([]);//=> true |
R.isNil() | 判断目标是否为null或者undefined | const isNil = R.isNil(null);//=> true |
R.keys() | 返回一个列表,其中包含所提供对象的所有可枚举属性的名称。 | R.keys({a: 1, b: 2, c: 3}); //=> ['a', 'b', 'c'] |
R.values() | 返回所提供对象的所有可枚举自身属性的列表。 | R.values({a: 1, b: 2, c: 3}); //=> [1, 2, 3] |
R.uniq() | 返回一个新列表,其中只包含原始列表中每个元素的一个副本。([...new Set()] 去重无法处理数组和对象) | R.uniq([1,2,1,1,'1',3,2,5,[6],[6],{c:'c'},{c:'c'},{c:'d'}]); //=> [1, 2, "1", 3, 5, [6],{c:'c'},{c:'d'}] |
R.zipObj() | 从键列表和值列表中创建新对象。键/值配对被截断为两个列表中较短者的长度。 | R.zipObj(['a', 'b', 'c', 'd'], [1, 2, 3]); //=> {a: 1, b: 2, c: 3} |
react 高阶组件
定义:高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
分类:代理方式、继承方式
作用:
- 代码复用
- 更改 props
- 通过 refs 获取组件实例
- 抽象 state
- 包装组件
const MyContainer = (WrappedComponent) => {
return class extends WrappedComponent {
render() {
const elementsTree = super.render();
let newProps = {};
if (elementsTree && elementsTree.type === 'input') {
newProps = {value: 'may the force be with you'};
}
const props = Object.assign({}, elementsTree.props, newProps);
const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children);
return newElementsTree;
}
}
}
export default MyContainer;
复制代码
react生命周期
componentWillMount()
、componentWillUpdata()
、componentWillReceiveProps()
在 v16.3 版本后不推荐使用,在 v17 版本计划删除。
componentWillMount()
被static getDerivedStateFromProps()
取代;componentWillUpdata()
被getSnapshotBeforeUpdate()
取代;componentWillReceiveProps()
应避免使用
安装阶段
在创建组件的实例并将其插入DOM时,将按以下顺序调用这些方法:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
更新
state的更改可能导致更新。重新渲染组件时,将按以下顺序调用这些方法:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
卸载
从DOM中删除组件时调用此方法:
componentWillUnmount()
setState()会触发哪些生命周期
static getDerivedStateFromProps()
→ shouldComponentUpdate()
→ render()
→ getSnapshotBeforeUpdate()
→ componentDidUpdate()
web前端开发,如何提高页面性能优化?
优化原则和方向
性能优化的原则是以更好的用户体验为标准,具体就是实现下面的 目标:
- 多使用内存、缓存或者其他方法
- 减少 CPU 和GPU 计算,更快展现
优化的 方向 有两个:
- 减少页面体积,提升网络加载
- 优化页面渲染
减少页面体积,提升网络加载
- 静态资源的压缩合并(JS 代码压缩合并、CSS 代码压缩合并、雪碧图)
- 静态资源缓存(资源名称加 MD5 戳,好处)
- 使用 CDN 让资源加载更快
优化页面渲染
- CSS 放前面,JS 放后面
- 懒加载(图片懒加载、下拉加载更多)
- 减少DOM 查询,对 DOM 查询做缓存
- 减少DOM 操作,多个操作尽量合并在一起执行(DocumentFragment)
- 事件节流
- 尽早执行操作(DOMContentLoaded)
- 使用 SSR(server side render,服务端渲染) 后端渲染,数据直接输出到 HTML 中,减少浏览器使用 JS 模板渲染页面 HTML 的时间(适合首屏渲染,利于SEO)
性能优化怎么做
上面提到的都是性能优化的单个点,性能优化项目具体实施起来,应该按照下面步骤推进:
- 建立性能数据收集平台,摸底当前性能数据,通过性能打点,将上述整个页面打开过程消耗时间记录下来
- 分析耗时较长时间段原因,寻找优化点,确定优化目标
- 开始优化
- 通过数据收集平台记录优化效果
- 不断调整优化点和预期目标,循环2~4步骤
性能优化是个长期的事情,不是一蹴而就的,应该本着 先摸底、再分析、后优化 的原则逐步来做。
内容方面:
-
减少 HTTP 请求 (Make Fewer HTTP Requests)
-
减少 DOM 元素数量 (Reduce the Number of DOM Elements)
-
使得 Ajax 可缓存 (Make Ajax Cacheable)
针对CSS:
-
把 CSS 放到代码页上端 (Put Stylesheets at the Top)
-
从页面中剥离 JavaScript 与 CSS (Make JavaScript and CSS External)
-
精简 JavaScript 与 CSS (Minify JavaScript and CSS)
-
避免 CSS 表达式 (Avoid CSS Expressions)
针对JavaScript :
-
脚本放到 HTML 代码页底部 (Put Scripts at the Bottom)
-
从页面中剥离 JavaScript 与 CSS (Make JavaScript and CSS External)
-
精简 JavaScript 与 CSS (Minify JavaScript and CSS)
-
移除重复脚本 (Remove Duplicate Scripts)
面向图片(Image):
-
优化图片
-
不要在 HTML 中使用缩放图片
-
使用恰当的图片格式
-
使用 CSS Sprites 技巧对图片优化
http状态码?
1xx:1开头的是信息状态码
2xx:2开头的是请求成功
3xx:3开头的是重定向
4xx:4开头的是客户端错误
5xx:5开头的是服务器错误
为什么使用React与Redux的,它们有什么优势
React(优势、劣势)
优势:
-
React速度很快 :它并不直接对DOM进行操作,引入了一个叫做虚拟DOM的概念,安插在javascript逻辑和实际的DOM之间,性能好。
-
跨浏览器兼容 :虚拟DOM帮助我们解决了跨浏览器问题,它为我们提供了标准化的API,甚至在IE8中都是没问题的。
-
一切都是component :代码更加模块化,重用代码更容易,可维护性高。
-
单向数据流 :Flux是一个用于在JavaScript应用中创建单向数据层的架构,它随着React视图库的开发而被Facebook概念化。
-
同构、纯粹的javascript :因为搜索引擎的爬虫程序依赖的是服务端响应而不是JavaScript的执行,预渲染你的应用有助于搜索引擎优化。
-
兼容性好 :比如使用RequireJS来加载和打包,而Browserify和Webpack适用于构建大型应用。它们使得那些艰难的任务不再让人望而生畏。
缺点:
- React本身只是一个V(视图库)而已,所以如果是大型项目想要一套完整的框架的话,也许还需要引入Redux和route相关的东西。
虚拟DOM
虚拟DOM是在DOM的基础上建立了一个抽象层,对数据和状态所做的任何改动,都会被自动且高效的同步到虚拟DOM,最后再批量同步到DOM中
在React中,render执行的结果得到的并不是真正的DOM节点,而仅仅是JavaScript对象,称之为 虚拟DOM。
innerHTML:render html字符串 + 重新创建所有 DOM 元素
虚拟DOM:render 虚拟DOM + diff + 更新必要的 DOM 元素
调用this.setState
会导致re-render
(重新渲染),但不会影响到整个页面,而只会影响组件本身及其children
组件。父母和兄弟姐妹都不会受到影响。当我们有一个层级很深的组件链时,这会让状态更新变得非常方便,因为我们只需要重绘(redraw
)它的一部分。
虚拟DOM的优点:
最终表现在DOM上的修改只是变更的部分,可以保证非常高效的渲染。
虚拟DOM的缺点:
首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。
Redux(优势)
Redux、Reflux、Mobx 对比
Redux VS Reflux:
- Reflux没有reducer的概念。取而代之,和action做互动的的是store。Reflux的功能流程如下:
组件就是用户界面,actions就是组件的动作,store用于执行actions的命令,并返回一个state对象给组件。组件通过state来更新界面。
而Redux的功能流程如下:
state就是数据,组件就是数据的呈现形式,action是动作,action是通过reducer来更新state的。
- Reflux可以直接调用action的方法,而Redux必须将方法绑定在组件的props上,或者使用props的dispatch方法来执行actions的方法。
Redux VS Mobx
- 单
store
与多store
。在Redux
中,你将所有的state
都放在一个全局的store
。这个store
对象就是你的单一数据源。另一方面,多个reducers
允许你修改不可变的state
。Mobx
则相反,它使用多stores
。 - Redux偏向于函数式编程,️而Mobx偏向于面向对象编程和响应式编程。Redux偏向于函数式编程,所以用的是纯函数,
State
不可变。而Mobx
的State
则可以改变。
React 与 Vue 对比
Vue特点
-
轻量级的框架
-
双向数据绑定 -- 比如你改变一个输入框 Input 标签的值,会自动同步更新到页面上其他绑定该输入框的组件的值
-
组件化 -- 页面上小到一个按钮都可以是一个单独的文件.vue,这些小组件直接可以像乐高积木一样通过互相引用而组装起来
-
单向响应的数据流
-
指令
-
插件化
React特点
-
声明式设计 -- React采用声明范式,可以轻松描述应用。
-
高效 -- React通过对DOM的模拟,最大限度地减少与DOM的交互。
-
灵活 -- −React可以与已知的库或框架很好地配合。
-
JSX -- JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
-
组件 -- 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
-
单向响应的数据流 -- React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
React 和 Vue 有许多相似之处
-
使用 Virtual DOM(虚拟 DOM通过 diff 比对,找到变更节点,重新渲染)
-
提供了响应式(Reactive)和组件化(Composable)的视图组件。
-
将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库。
-
使用Prop传递数据,prop 是单向绑定的,当父组件的属性变化时,将传导给子组件,但是不会反过来。子组件不应该直接改变prop的值。
-
都提供了路由、状态管理器(React对应的Redux,Vue对应Vuex)等。
-
都提供合理的钩子函数,可以让开发者定制化地去处理需求。
-
在组件开发中都支持mixins的特性。
Vue 与 React 差异
性能上
-
React 和 Vue 在大部分常见场景下都能提供近似的性能。通常 Vue 会有少量优势,因为 Vue 的 Virtual DOM 实现相对更为轻量一些。
-
在 React 应用中,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树。如要避免不必要的子组件的重渲染,有相应的处理机制PureComponent(React.Component 与 React.PureComponent)。在 Vue 应用中,组件的依赖是在渲染过程中自动追踪的,所以系统能精确知晓哪个组件确实需要被重渲染。
-
用 Vue 和 React 开发大多数应用的速度都是足够快的。假如你要开发一个对性能要求比较高的数据可视化或者动画的应用时,你需要了解到下面这点:在开发中,Vue 每秒最高处理 10 帧,而 React 每秒最高处理不到 1 帧。
HTML & CSS
-
在 React 中,一切都是 JavaScript。HTML 可以用 JSX 来表达。Vue 的整体思想是拥抱经典的 Web 技术(采用template方式,比如v-on的各种修饰符,在 JSX 中实现对应的功能会需要多得多的代码),事实上 Vue 也提供了render渲染函数,甚至支持 JSX。
-
在 React 中,现在的潮流也越来越多地将 CSS 也纳入到 JavaScript 中来处理(有其优越性,具体不详说),通过依赖引入css模块,而 Vue 可以让你在每个单文件组件中完全访问 CSS,方便的规定css作用域,也可引入css模块。
其他
-
两者另一个重要差异是,Vue 的路由库和状态管理库都是由官方维护支持且与核心库同步更新的。React 则是选择把这些问题交给社区维护,因此创建了一个更分散的生态系统。但相对的,React 的生态系统相比 Vue 更加繁荣。
-
从两者提供的路由、状态管理器等向上扩展来看,Vue、React做得都比较完善,从向下扩展来看,Vue就类似于 jQuery。你只要把如下标签放到页面就可以运行:
<script src="https://unpkg.com/vue/dist/vue.js"></script>
-
本地渲染。ReactNative 能使你用相同的组件模型编写有本地渲染能力的 APP(iOS 和 Android)。能同时跨多平台开发,对开发者是非常棒的。相应地,Vue 和Weex会进行官方合作,Weex 是阿里的跨平台用户界面开发框架,Weex 的 JavaScript 框架运行时用的就是 Vue。这意味着在 Weex 的帮助下,你使用 Vue 语法开发的组件不仅仅可以运行在浏览器端,还能被用于开发 iOS 和 Android 上的原生应用。当然在现在,Weex 还在积极发展,成熟度也不能和 ReactNative 相抗衡。
-
Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作DOM。
axios
axios 是目前最常用的 http 请求库,可以用于浏览器和 node.js
Axios 的主要特性包括:
基于 Promise
- 支持浏览器和 node.js
- 可添加拦截器从而转换请求和响应数据
- 请求可以取消(axios.CancelToken)
- 自动转换 JSON 数据
- 客户端支持防范 XSRF (跨站请求伪造)
- 支持各主流浏览器及 IE8+
手写 XMLHttpRequest 不借助任何库
let xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
// 这里的函数异步执行,可参考之前 JS 基础中的异步模块
if (xhr.readyState == 4) {
if (xhr.status == 200) {
alert(xhr.responseText)
}
}
}
xhr.open("GET", "/api", false)
xhr.send(null)
复制代码
状态码说明
上述代码中,有两处状态码需要说明。xhr.readyState
是浏览器判断请求过程中各个阶段的,xhr.status
是 HTTP 协议中规定的不同结果的返回状态说明。
xhr.readyState
的状态码说明:
-
0 -代理被创建,但尚未调用
open()
方法。 -
1 -
open()
方法已经被调用。 -
2 -
send()
方法已经被调用,并且头部和状态已经可获得。 -
3 -下载中,
responseText
属性已经包含部分数据。 -
4 -下载操作已完成
xhr.status
即 HTTP 状态码,有 2xx 3xx 4xx 5xx 这几种,比较常用的有以下几种:
- 200 正常
- 3xx
- 301 永久重定向。如http://xxx.com这个 GET 请求(最后没有/),就会被301到http://xxx.com/(最后是/)
- 302 临时重定向。临时的,不是永久的
- 304 资源找到但是不符合请求条件,不会返回任何主体。如发送 GET 请求时,head 中有If-Modified-Since: xxx(要求返回更新时间是xxx时间之后的资源),如果此时服务器 端资源未更新,则会返回304,即不符合要求
- 404 找不到资源
- 5xx 服务器端出错了
前端跨域
同源策略:同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能。所谓同源指的是协议、域名、端口均相同。
跨域:浏览器从一个域名的网页去请求另一个域名的资源时,协议、域名、端口有一个不同(违反同源策略),即视为跨域。
Jsonp
利用 src
属性不受同源策略影响实现跨域。例如<script/>
、<img/>
、<iframe/>
标签。
注意src中带上回调函数callback
。
<script type="text/javascript" src="http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler"></script>
<script type="text/javascript" src=''>
// 得到航班信息查询结果后的回调函数
var flightHandler = function(data){
alert('你查询的航班结果是:票价 ' + data.price + ' 元,' + '余票 ' + data.tickets + ' 张。');
};
</script>
复制代码
iframe跨子域
基于iframe实现的跨域要求两个域具有aa.xx.com,bb.xx.com 这种特点, 也就是两个页面必须属于一个基础域(例如都是xxx.com),使用同一协议和同一端口,这样在两个页面中同时添加document.domain,就可以实现父页面调用子页面的函数。
eg:a.study.cn/a.html 请求 b.study.cn/b.html
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
document.domain = 'study.cn';
function test() {
alert(document.getElementById('a').contentWindow);
}
</script>
</head>
<body>
<iframe id='a' src='http://b.study.cn/b.html' onload='test()'>
</body>
复制代码
Ajax
JQuery已经将跨域封装到了Ajax中
<script type="text/javascript">
$(document).ready(function(){
var name = 'chenshishuo';
var sex = 'man';
var address = 'shenzhen';
var looks = 'handsome ';
$.ajax({
type : 'get',
url:'http://192.168.31.137/train/test/testjsonp',
data : {
name : name,
sex : sex,
address : address,
looks : looks,
},
cache :false,
jsonp: "callback",
jsonpCallback:"success",
dataType : 'jsonp',
success:function(data){
alert(data);
},
error:function(data){
alert('error');
}
});
});
</script>
复制代码
后端添加白名单
token 与 session
为什么要有token与session?——身份验证,用以判断两次请求是否予以通过
session生成方式?
浏览器第一次访问服务器,服务器会创建一个session,然后同时为该session生成一个唯一的会话的key,也就是sessionid。然后服务器再把sessionid,以cookie的形式发送给客户端。这样浏览器下次再访问时,会直接带着cookie中的sessionid。然后服务器根据sessionid找到对应的session进行匹配;
还有一种是浏览器禁用了cookie或不支持cookie,这种可以通过URL重写的方式发到服务器;
简单来讲,用户访问的时候说他自己是张三,他骗你怎么办? 那就在服务器端保存张三的信息,给他一个id,让他下次用id访问。
token的生成方式?
浏览器第一次访问服务器,根据传过来的唯一标识userId,服务端会通过一些算法,如常用的HMAC-SHA256算法,然后加一个密钥,生成一个token,然后通过BASE64编码一下之后将这个token发送给客户端;客户端将token保存起来,下次请求时,带着token,服务器收到请求后,然后会用相同的算法和密钥去验证token,如果通过,执行业务操作,不通过,返回不通过信息;
token和session的区别?
token和session其实都是为了身份验证,session一般翻译为会话,而token更多的时候是翻译为令牌; session服务器会保存一份,可能保存到缓存,文件,数据库;同样,session和token都是有过期时间一说,都需要去管理过期时间;
虽然确实都是“客户端记录,每次访问携带”,但 token 很容易设计为自包含的,也就是说,后端不需要记录什么东西,每次一个无状态请求,每次解密验证,每次当场得出合法 /非法的结论。这一切判断依据,除了固化在 CS 两端的一些逻辑之外,整个信息是自包含的。这才是真正的无状态。 而 sessionid ,一般都是一段随机字符串,需要到后端去检索 id 的有效性。万一服务器重启导致内存里的 session 没了呢?万一 redis 服务器挂了呢?
方案 A(session) :我发给你一张身份证,但只是一张写着身份证号码的纸片。你每次来办事,我去后台查一下你的 id 是不是有效。
方案 B(token) :我发给你一张加密的身份证,以后你只要出示这张卡片,我就知道你一定是自己人。
转Boolean
在条件判断时,除了 undefined
, null
, false
, NaN
, ''
, 0
, -0
,其他所有值都转为 true
,包括所有对象。
[]
与{}
都会转为Number
,[]
会转为0
,{}
会转为NaN
if ([] == false) console.log(1);
if ([]) console.log(2);
if ({} == false ) console.log(3);
if ({}) console.log(4);
if ([1] == [1]) console.log(5);
// => 1 2 4
复制代码
关于[]
和{}
需要注意的点:
- 空数组
[]
和空对象{}
都是object
类型,因此直接用于if
判断条件时就会被转化为true
。 - 任意值与布尔值比较,都会将两边的值转化为
Number
。 - 如果将空数组
[]
与布尔值false
比较,false
转化为0
,而空数组[]
也转化为0
,因此[] == false
的判断得到true
。 - 如果将空对象
{}
与布尔值false
比较,false
转化为0
,而空对象{}
转化为NaN
,由于NaN
与任何数都不相等,因此{} == false
的判断得到false
。 - 引用类型之间的比较是内存地址的比较,不需要进行隐式转换,所以
[] == [] //false
地址不一样
console.log(([0]) ? true : false); // true
console.log(([0] == false) ? true : false); // true
console.log(({x:0} == false) ? true : false); // false
复制代码
[0]
直接用于if
判断条件时会被转化为true
。 与布尔值比较,都会将两边的值转化为Number
,[0]
转换为0
,{x:0}
转换为NaN
。
深浅拷贝
浅拷贝
首先可以通过 Object.assign
来解决这个问题。当然我们也可以通过展开运算符 …
来解决
let a = {
age: 1
}
let b = Object.assign({}, a)
let c = {...a}
a.age = 2
console.log(b.age) // 1
console.log(c.age) // 1
复制代码
通常浅拷贝就能解决大部分问题了,但是当我们遇到如下情况就需要使用到深拷贝了
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = {...a}
a.jobs.first = 'native'
console.log(b.jobs.first) // native
复制代码
浅拷贝只解决了第一层的问题,如果接下去的值中还有对象的话,那么就又回到刚开始的话题了,两者享有相同的引用。要解决这个问题,我们需要引入深拷贝。
深拷贝
这个问题 通常 可以通过 JSON.parse(JSON.stringify(object))
来解决。
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
复制代码
但是该方法也是有局限性的:
- 会忽略 undefined
- 不能序列化函数
- 不能解决循环引用的对象
$.extend([deep],target,Object1,Object2)
deep
为布尔值,默认不传为false(如果传值只能传true),为true时表示深拷贝
或者借助Ramda
函数库,Ramda.clone()
关于Promise
Promise
Promise 是异步编程的一种解决方案,用于抽象异步处理对象。可以避免回调地狱。
Promise 对象有以下两个特点:
-
对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:
pending(进行中)
、fulfilled(已成功)
和rejected(已失败)
。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。 -
一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从
pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved
(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
await & async
使用注意点:
-
await
命令后面的Promise
对象,运行结果可能是rejected
,所以最好把await
命令放在try...catch
代码块中。async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另一种写法 async function myFunction() { await somethingThatReturnsAPromise() .catch(function (err) { console.log(err); }); } 复制代码
-
多个
await
命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。let foo = await getFoo(); let bar = await getBar(); 复制代码
上面代码中,
getFoo
和getBar
是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo
完成以后,才会执行getBar
,完全可以让它们同时触发。// 写法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise; 复制代码
上面两种写法,getFoo
和 getBar
都是同时触发,这样就会缩短程序的执行时间。
await
命令只能用在async
函数之中,如果用在普通函数,就会报错。
JS异步机制
主线程、执行栈、任务队列:
- 主线程不断读取执行栈中的同步事件,直到执行栈空
- 异步任务结束放入任务队列,执行栈空时主线程读取任务队列
- 任务队列读取完毕,回到步骤1
event loop是一个执行模型,在不同的地方有不同的实现。
-
js是单线程的
-
js进程分为宏任务与微任务,优先执行微任务,后执行宏任务(其实不准确,页面加载先执行一个
<script>
标签,属于宏任务),若微任务执行过程中产生了新的微任务,则将改新产生的微任务置于队列底部(例如队列为A -> B -> C,执行A过程中产生了微任务A1,则将A1置于C后面A -> B -> C -> A1) -
常见的微任务:
promise
,process.nextTick
(Node独有,以及其他不太常见的本次不讨论) -
常见的宏任务:
setTimeout
,setInterval
,setImmediate
(Node独有,以及其他不太常见的本次不讨论) -
关于
process.nextTic
,优先级promise
>process.nextTick
>promise.then
,例如:new Promise(function(resolve) { console.log('1'); resolve(); }).then(function() { console.log('2') }) process.nextTick(function() { console.log('3'); }) 输出结果:1,3,2 复制代码
setImmediate(function () { console.log(1); }, 0); setTimeout(function () { console.log(2); }, 0); new Promise(function (resolve) { console.log(3); resolve(); console.log(4); }).then(function () { console.log(5); }); console.log(6); process.nextTick(function () { console.log(7); }); console.log(8); // 3 4 6 8 7 5 1 2 复制代码
async function async1 () { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2 () { console.log('async2'); } console.log('script start'); setTimeout(function () { console.log('setTimeout'); }, 0); async1(); new Promise(function (resolve) { console.log('promise1'); resolve(); }).then(function () { console.log('promise2'); }); console.log('script end'); 输出结果: script start async1 start async2 promise1 script end async1 end promise2 setTimeout 复制代码
清空和截短数组
最简单的清空和截短数组的方法就是改变 length
属性:
const arr = [11, 22, 33, 44, 55, 66];
// 截取
arr.length = 3;
console.log(arr); //=> [11, 22, 33];
// 清空
arr.length = 0;
console.log(arr); //=> []
console.log(arr[2]); //=> undefined
复制代码
扁平化数组
使用扩展运算符可以快速扁平化数组(或直接使用ES6的flat()
):
const arr = [11, [22, 33], [44, 55], 66];
const flatArr = [].concat(...arr); // => [11, 22, 33, 44, 55, 66]
const flatArr2 = arr.flat(); // => [11, 22, 33, 44, 55, 66]
复制代码
不幸的是,上面的技巧只能适用 二维数组 ,但是使用递归,我们可以扁平化任意纬度数组:
function flattenArray(arr) {
const flattened = [].concat(...arr);
return flattened.some(item => Array.isArray(item)) ?
flattenArray(flattened) : flattened;
}
const arr = [11, [22, 33], [44, [55, 66, [77, [88]], 99]]];
const flatArr = flattenArray(arr); //=> [11, 22, 33, 44, 55, 66, 77, 88, 99]
复制代码
或者使用ramda
函数,Ramda.flatten()
let arr = [1,2,[4,{name:'jack'},[5,{name:'tom',info:{add:'wuhan'}}]]]
R.flatten(arr) //=> [1,2,4,{name:'jack'},5,{name:'tom',info:{add:'wuhan'}}]
复制代码
事件委托
部分位运算的使用
// 生成六位随机数(数字字母组成)
// 先生成随机数然后转成36进制字符串(36进制 0-9a-z),然后在小数位取六位
Math.random().toString(36).slice(2,8)
(~~(Math.random()*(1<<30))).toString(36)
复制代码
实现千分位
例如:1234567890 => 1,234,567,890
最优实现:
正则表达式的零宽断言 /\d{1,3}(?=(\d{3})+$)/g
let a = 12345678900;
let b = a.toString(); // b => '1234567890'
let c = b.replace(/\d{1,3}(?=(\d{3})+$)/g, res => res + ','); // c => '1,234,567,890'
复制代码
或者(不用正则)函数实现:
const qianfen = number => {
let earr = [];
let arr = number.toString().split('').reverse();
arr.forEach((i,idx) => earr.push((idx != arr.length - 1 && idx % 3 == 2) ? ',' + i : i));
return earr.reverse().join('');
}
console.log(qianfen(123456789)); // => 123,456,789
console.log(qianfen(1234567890)); // => 1,234,567,890
复制代码
匹配特定关键字并使其变颜色
思路:先将特定关键字取出改变,然后再渲染。此时用到
dangerouslySetInnerHTML
属性
render() {
let words = '杭州杭城科技有限公司';
let keys = '杭';
let reg = new RegExp(keys, 'ig'); // reg => /杭/gi
const showw = w => {
//let ss = w.replace(/杭/ig,"<b style='color: green;'>$&</b>");
let ss = w.replace(reg,"<b style='color: green;'>$&</b>");
return ss;
}
let kk = showw(words);
return (<div className="card">
<div className='show' style={{color:'red'}} dangerouslySetInnerHTML={{__html:kk}}></div>
</div>)
}
复制代码
缓存
在介绍缓存的时候,我们习惯将缓存分为 强缓存 和 协商缓存 两种。两者的主要区别是使用本地缓存的时候,是否需要向服务器验证本地缓存是否依旧有效。
三次握手四次挥手
实现超出整数存储范围的两个大整数相加function add(a,b)。注意a和b以及函数的返回值都是字符串。
function add (a, b) {
let lenA = a.length,
lenB = b.length,
len = lenA > lenB ? lenA : lenB;
// 先补齐位数一致
if(lenA > lenB) {
for(let i = 0; i < lenA - lenB; i++) {
b = '0' + b;
}
} else {
for(let i = 0; i < lenB - lenA; i++) {
a = '0' + a;
}
}
// arr 存储最终结果的数组,carryAdd 逐位相加时产生的进位
let arrA = a.split('').reverse(),
arrB = b.split('').reverse(),
arr = [],
carryAdd = 0;
// 逐位相加。若产生进位则carryAdd 为 1,否则carryAdd 为 0
// carryAdd 逐位存储两数逐位相加产生的个位
for(let i = 0; i < len; i++) {
let temp = Number(arrA[i]) + Number(arrB[i]) + carryAdd;
arr[i] = temp > 9 ? temp - 10 : temp;
carryAdd = temp >= 10 ? 1 : 0;
}
// 最后判断一次首位是否产生进位
if(carryAdd === 1) {
arr[len] = 1;
}
return arr.reverse().join('');
}
复制代码
节流与防抖
函数节流
是指 一定时间内js方法只跑一次。比如人的眨眼睛,就是一定时间内眨一次。这是函数节流最形象的解释。
函数防抖
是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷卡了,司机才开车。
// 函数节流
let flag = false;
const throttling = (func, ms = 500) => {
if (flag) return;
flag = true;
setTimeout(() => {
func();
flag = false;
}, ms);
};
// 思路:定义一个flag,如果当前是空闲的则执行函数,如果当前非空闲,直接return出去。
// setTimeout()用于在固定时间后将状态设置为空闲。也就是固定时间(ms)只能执行一次js。
// 存在的问题:如果执行函数耗时超过了ms 那么还是会出现上一次没执行完又执行下一次请求的情况
复制代码
// 函数防抖
let timer = 0;
const debounce = (func, ms = 500) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func();
}, ms);
};
// 思路:设置一个定时器,延迟处理请求函数;如果等待时间await之后没人执行请求,则执行函数
// 如果用户调用该函数的间隔小于wait的情况下,上一次的时间还未到就被清除了,并不会执行函数
复制代码
图片大小计算
大小 = 分辨率 * 位深/8 (/8计算的是字节数。)
分辨率 = 宽 * 高(如:1024 * 768,640 * 480)
位深:如24位,16位,8位
例如: 一幅图像分辨率:1024*768,24位,则其大小计算如下:
大小 = 1024 * 768 * 24 / 8 = 2359296byte = 2304KB
浏览器从加载页面到渲染页面的过程(输入URL后发生了什么)
加载过程
要点如下:
- 浏览器根据
DNS
服务器得到域名的IP
地址 (中间有三次握手) - 向这个
IP
的机器发送HTTP
请求 - 服务器收到、处理并返回
HTTP
请求 - 浏览器得到返回内容
例如在浏览器输入https://juejin.im/timeline,然后经过 DNS 解析,juejin.im对应的 IP 是36.248.217.149(不同时间、地点对应的 IP 可能会不同)。然后浏览器向该 IP 发送 HTTP 请求。server 端接收到 HTTP 请求,然后经过计算(向不同的用户推送不同的内容),返回 HTTP 请求,返回一堆 HMTL 格式的字符串,因为只有 HTML格式浏览器才能正确解析。接下来就是浏览器的渲染过程。
渲染过程
要点如下:
- 根据
HTML
结构生成DOM
树 - 根据
CSS
生成CSSOM
- 将
DOM
和CSSOM
整合形成RenderTree
- 根据
RenderTree
开始渲染和展示 - 遇到
<script>
时,会执行并阻塞渲染
重绘与回流
当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。
-
重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少
-
回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:
-
页面初次渲染
-
浏览器窗口大小改变
-
元素尺寸、位置、内容发生改变
-
元素字体大小变化
-
添加或者删除可见的 dom 元素
-
激活 CSS 伪类(例如::hover)
-
查询某些属性或调用某些方法:
- clientWidth、clientHeight、clientTop、clientLeft
- offsetWidth、offsetHeight、offsetTop、offsetLeft
- scrollWidth、scrollHeight、scrollTop、scrollLeft
- getComputedStyle()
- getBoundingClientRect()
- scrollTo()
-
回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。
Web 安全
题目:前端常见的安全问题有哪些?
Web 前端的安全问题,能回答出下文的两个问题,这个题目就能基本过关了。开始之前,先说一个最简单的攻击方式 —— SQL 注入。
上学的时候就知道有一个「SQL注入」的攻击方式。例如做一个系统的登录界面,输入用户名和密码,提交之后,后端直接拿到数据就拼接 SQL 语句去查询数据库。如果在输入时进行了恶意的 SQL 拼装,那么最后生成的 SQL 就会有问题。但是现在稍微大型一点的系统,都不会这么做,从提交登录信息到最后拿到授权,要经过层层的验证。因此,SQL 注入都只出现在比较低端小型的系统上。
XSS(Cross Site Scripting,跨站脚本攻击)
这是前端最常见的攻击方式,很多大型网站(如 Facebook)都被 XSS 攻击过。
举一个例子,我在一个博客网站正常发表一篇文章,输入汉字、英文和图片,完全没有问题。但是如果我写的是恶意的 JS 脚本,例如获取到document.cookie然后传输到自己的服务器上,那我这篇博客的每一次浏览都会执行这个脚本,都会把访客 cookie 中的信息偷偷传递到我的服务器上来。
其实原理上就是黑客通过某种方式(发布文章、发布评论等)将一段特定的 JS 代码隐蔽地输入进去。然后别人再看这篇文章或者评论时,之前注入的这段 JS 代码就执行了。JS 代码一旦执行,那可就不受控制了,因为它跟网页原有的 JS 有同样的权限,例如可以获取 server 端数据、可以获取 cookie 等。于是,攻击就这样发生了。
XSS的危害
XSS 的危害相当大,如果页面可以随意执行别人不安全的 JS 代码,轻则会让页面错乱、功能缺失,重则会造成用户的信息泄露。
比如早些年社交网站经常爆出 XSS 蠕虫,通过发布的文章内插入 JS,用户访问了感染不安全 JS 注入的文章,会自动重新发布新的文章,这样的文章会通过推荐系统进入到每个用户的文章列表面前,很快就会造成大规模的感染。
还有利用获取 cookie 的方式,将 cookie 传入入侵者的服务器上,入侵者就可以模拟 cookie 登录网站,对用户的信息进行篡改。
XSS的预防
那么如何预防 XSS 攻击呢?—— 最根本的方式,就是对用户输入的内容进行验证和替换,需要替换的字符有:
& 替换为:&
< 替换为:<
> 替换为:>
” 替换为:"
‘ 替换为:'
/ 替换为:/
复制代码
替换了这些字符之后,黑客输入的攻击代码就会失效,XSS 攻击将不会轻易发生。
除此之外,还可以通过对 cookie 进行较强的控制,比如对敏感的 cookie 增加http-only限制,让 JS 获取不到 cookie 的内容。
CSRF(Cross-site request forgery,跨站请求伪造)
CSRF 是借用了当前操作者的权限来偷偷地完成某个操作,而不是拿到用户的信息。
例如,一个支付类网站,给他人转账的接口是https://user-gold-cdn.xitu.io/2019/2/17/168f9547e3ae02cf
,而这个接口在使用时没有任何密码或者 token
的验证,只要打开访问就直接给他人转账。一个用户已经登录了http://buy.com
,在选择商品时,突然收到一封邮件,而这封邮件正文有这么一行代码<img src="https://user-gold-cdn.xitu.io/2019/2/17/168f9547e3ae02cf"/>
,他访问了邮件之后,其实就已经完成了购买。
CSRF 原理示意图:
CSRF 的发生其实是借助了一个 cookie
的特性。我们知道,登录了http://buy.com
之后,cookie
就会有登录过的标记了,此时请求https://user-gold-cdn.xitu.io/2019/2/17/168f9547e3ae02cf
是会带着 cookie
的,因此 server
端就知道已经登录了。而如果在 http://buy.com
去请求其他域名的 API 例如http://abc.com/api
时,是不会带 cookie
的,这是浏览器的同源策略的限制。但是 —— 此时在其他域名的页面中,请求https://user-gold-cdn.xitu.io/2019/2/17/168f9547e3ae02cf
,会带着buy.com
的 cookie
,这是发生 CSRF 攻击的理论基础。
预防 CSRF 就是加入各个层级的权限验证,例如现在的购物网站,只要涉及现金交易,肯定要输入密码或者指纹才行。除此之外,敏感的接口使用POST请求而不是GET 也是很重要的。
何为构建工具
“构建”也可理解为“编译”,就是将开发环境的代码转换成运行环境代码的过程。开发环境的代码是为了更好地阅读,而运行环境的代码是为了更快地执行,两者目的不一样,因此代码形式也不一样。例如,开发环境写的 JS 代码,要通过混淆压缩之后才能放在线上运行,因为这样代码体积更小,而且对代码执行不会有任何影响。总结一下需要构建工具处理的几种情况:
- 处理模块化:CSS 和 JS 的模块化语法,目前都无法被浏览器兼容。因此,开发环境可以使用既定的模块化语法,但是需要构建工具将模块化语法编译为浏览器可识别形式。例如,使用 webpack、Rollup 等处理 JS 模块化。
- 编译语法:编写 CSS 时使用 Less、Sass,编写 JS 时使用 ES6、TypeScript 等。这些标准目前也都无法被浏览器兼容,因此需要构建工具编译,例如使用 Babel 编译 ES6 语法。
- 代码压缩:将 CSS、JS 代码混淆压缩,为了让代码体积更小,加载更快。
进程与线程区别?JS 单线程带来的好处?
本质上来说,两个名词都是 CPU工作时间片的一个描述。
进程 描述了 CPU 在运行指令及加载和保存上下文所需的时间,放在应用上来说就代表了一个程序。线程 是进程中的更小单位,描述了执行一段指令所需的时间。
把这些概念拿到浏览器中来说,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。
如果在 JS 执行的时候 UI 线程还在工作,就可能导致不能安全的渲染 UI。这其实也是一个单线程的好处,得益于 JS 是单线程运行的,可以达到节省内存,节约上下文切换时间,没有锁的问题的好处。
执行栈
可以把执行栈认为是一个 存储函数调用的栈结构 ,遵循先进后出的原则。
instanceof 的原理是什么?
instanceof 可以正确的判断对象的类型,因为内部机制是 通过判断对象的原型链中是不是能找到类型的 prototype
。
原型 / 构造函数 / 实例
-
原型(prototype): 一个简单的对象,用于实现对象的 属性继承。可以简单的理解成对象的爹。在 Firefox 和 Chrome 中,每个JavaScript对象中都包含一个__proto__ (非标准)的属性指向它爹(该对象的原型),可obj.__proto__进行访问。
-
构造函数: 可以通过new来 新建一个对象 的函数。
-
实例: 通过构造函数和new创建出来的对象,便是实例。 实例通过__proto__指向原型,通过constructor指向构造函数。
举个栗子,以 Object
为例,我们常用的 Object
便是一个构造函数,因此我们可以通过它构建实例。
// 实例
const instance = new Object()
复制代码
则此时, 实例为 instance
,构造函数为 Object
,我们知道,构造函数拥有一个 prototype
的属性指向原型,因此原型为:
// 原型
const prototype = Object.prototype
复制代码
这里我们可以来看出三者的关系:
实例.__proto__ === 原型
原型.constructor === 构造函数
构造函数.prototype === 原型
// 这条线其实是是基于原型进行获取的,可以理解成一条基于原型的映射线
// 例如:
// const o = new Object()
// o.constructor === Object --> true
// o.__proto__ = null;
// o.constructor === Object --> false
实例.constructor === 构造函数
复制代码
关系图如下:
let shili = new Object();
let yuanxin = Object.prototype;
实例: shili
构造函数: Object 或 shili.constructor 或 yuanxin.constructor
原型:yuanxin (Object.prototype) 或 shili.__proto__
复制代码
原型与原型链
其实每个 JS 对象都有 __proto__
属性,这个属性指向了原型。
原型 也是一个对象,并且这个对象中包含了很多函数,我们可以得出一个结论:对于 obj 来说,可以通过 __proto__
找到一个原型对象,在该对象中定义了很多函数让我们来使用。
原型的
constructor
属性指向构造函数,构造函数又通过prototype
属性指回原型,但是并不是所有函数都具有这个属性,Function.prototype.bind()
就没有这个属性。
其实 原型链 就是多个对象通过 __proto__
的方式连接了起来。
- Object 是所有对象的爸爸,所有对象都可以通过 proto 找到它
- Function 是所有函数的爸爸,所有函数都可以通过 proto 找到它
- 函数的 prototype 是一个对象
- 对象的 proto 属性指向原型, proto 将对象和原型连接起来组成了原型链
模块化
模块化就是 将文件按照功能分离,根据需求引入不同的文件中 。源于服务器端。
使用模块化可以给我们带来以下好处:
- 解决命名冲突
- 提供复用性
- 提高代码可维护性
Proxy
代理模式(英语:Proxy Pattern)是程序设计中的一种设计模式。
在MDN上对于 Proxy 的解释是:
Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
简单来说: Proxy 对象就是可以让你去对JavaScript中的一切合法对象的基本操作进行自定义。然后用你自定义的操作去覆盖其对象的基本操作。也就是当一个对象去执行一个基本操作时,其执行的过程和结果是你自定义的,而不是对象的。
Proxy的作用 :
对于代理模式 Proxy 的作用主要体现在三个方面:
-
拦截和监视外部对对象的访问
-
降低函数或类的复杂度
-
在复杂操作前对操作进行校验或对所需资源进行管理
路由原理
前端路由原理?两种实现方式有什么区别?
前端路由实现起来其实很简单,本质就是 监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新页面 。目前前端使用的路由就只有两种实现方式:
- Hash 模式
- History 模式
两种模式对比
- Hash 模式只可以更改 # 后面的内容,History 模式可以通过 API 设置任意的同源 URL
- History 模式可以通过 API 添加任意类型的数据到历史记录中,Hash 模式只能更改哈希值,也就是字符串
- Hash 模式无需后端配置,并且兼容性好。History 模式在用户手动输入地址或者刷新页面的时候会发起 URL 请求,后端需要配置 index.html 页面用于匹配不到静态资源的时候
Babel
Babel 是一个 JavaScript 编译器
Babel 是一个工具链,主要用于在旧的浏览器或环境中将 ECMAScript 2015+ 代码转换为向后兼容版本的 JavaScript 代码:
- 转换语法
- Polyfill 实现目标环境中缺少的功能 (通过 @babel/polyfill)
- 源代码转换 (codemods)
特性 :
- Babel 可以转换 JSX 语法
- Babel 可以删除类型注释!请注意 Babel 不会进行类型检查;你仍然可以安装使用 Flow 或者 TypeScript 来进行类型检查。
- 可插拔。Babel 是用插件构建的。你可以使用现有插件编写自己的转换管道或编写自己的插件。通过使用或创建 preset 轻松使用一组插件。
- 可调试。支持 Source map ,因此你可以轻松调试编译过的代码。
- 压缩性。Babel 尝试使用尽可能少的代码而不依赖于庞大的运行时环境。
受控组件与非受控组件
受控组件
组件状态由state控制。假设我们现在有一个表单,表单中有一个input标签,input的value值必须是我们设置在constructor构造函数的state中的值,然后,通过onChange触发事件来改变state中保存的value值,这样形成一个循环的回路影响。也可以说是React负责渲染表单的组件仍然控制用户后续输入时所发生的变化。
非受控组件
组件状态不由
state
控制。其值可以通过refs
获取。常见的有input(不添加value
、defaultvalue
、onChange()
等)
handleSubmit = event => {
const val = this.refs.inputRef.value;
alert('A name was submitted: ' + val);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref='inputRef'/>
</label>
<input type="submit" value="Submit" />
</form>
);
}
复制代码
浏览器内核
函数 | 作用 |
---|---|
Trident | IE内核 |
Gecko | Firefox浏览器内核 |
Webkit | Safari浏览器内核 |
Presto | Opera浏览器内核,最初是自己的Presto内核,后来是Webkit,现在是Blink内核 |
Chromium | 统称为Chromium内核或Chrome内核,以前是Webkit内核,现在是Blink内核 |
js快速排序
const b = [1,4,6,3,7,4,6,3,2,9];
const quickSort = arr => {
const len = arr.length;
if (len <= 1) return arr;
const s = Math.floor(len / 2);
const temp = arr.splice(s, 1);
let left=[],
right=[];
arr.forEach(i => i < temp ? left.push(i) : right.push(i));
return quickSort(left).concat(temp, quickSort(right));
}
console.log(quickSort(b)); // => [1, 2, 3, 3, 4, 4, 6, 6, 7, 9]
复制代码
盒模型
页面渲染时,
dom
元素所采用的 布局模型。可通过box-sizing
进行设置。根据计算宽高的区域可分为:
content-box
(W3C
标准盒模型)border-box
(IE
盒模型)padding-box
(仅Firefox
曾实现,且已在Firefox 50
版本中被删除)
盒模型包括
margin
、border
、padding
、content
区别:
content-box
计算时content
不包含 border
与 padding
,而 border-box
的content
则包含 border
与 padding
。
示例:
.box {
width: 200px;
height:100px;
margin:10px;
padding:5px;
border:1px;
box-sizing: ···
}
复制代码
若设置 box-sizing: content-box;
,则box
宽度为: 200 + (10 + 5 + 1) * 2 = 232px;
,content
部分宽度为 200px
;
若设置 box-sizing: border-box;
,则box
宽度为: 200 + 10 * 2 = 220px;
,content
部分宽度为 200 - (10 + 1) * 2 = 178px
;
BFC
块格式化上下文(Block Formatting Context,BFC) 是Web页面的可视化CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。
常见创建 BFC
方法:
- 浮动元素(元素的 float 不是 none)
- 绝对定位元素(元素的 position 为 absolute 或 fixed)
- 行内块元素(元素的 display 为 inline-block)
- overflow 值不为 visible 的块元素
- display 值为 flow-root 的元素
- 弹性元素(display为 flex 或 inline-flex元素的直接子元素)
- 网格元素(display为 grid 或 inline-grid 元素的直接子元素
作用:
- 让浮动内容和周围的内容等高
- 防止外边距塌陷
选择器优先级
!important
> 行内样式 > #id
> .class
> tag
> *
> 继承 > 默认
去除浮动影响,防止父级高度塌陷
- 通过增加尾元素清除浮动
:after
/<br>
/clear: both
- 创建父级 BFC
- 父级设置高度
link 与 @import 的区别
- link功能较多,可以定义 RSS,定义 Rel 等作用,而@import只能用于加载 css
- 当解析到link时,页面会同步加载所引的 css,而@import所引用的 css 会等到页面加载完才被加载
- @import需要 IE5 以上才能使用 link可以使用 js 动态引入,@import不行