个人博客新地址→ 点我♪(^∀^●)ノ
React和Vue对比
相同点:
- 数据驱动视图,提供了响应式(Reactive)和组件化(Composable)的视图组件。
- 都有Virtual DOM,组件化开发,通过props参数进行父子组件数据的传递,都实现webComponents规范
- 数据流动单向
- 都支持服务端渲染
- 都有支持native的方案,React的
React native
,Vue的weex
不同点:
社区:React社区还是要比Vue大很多;
开发模式:React在view层侵入性还是要比Vue大很多的,React严格上只针对MVC的view层,Vue则是MVVM模式的一种实现;
数据绑定:Vue有实现了双向数据绑定,React需要自己实现
数据渲染:对于大规模数据渲染,React要比Vue更快,Vue渲染机制启动时候要做的工作比较多;
数据更新方面:Vue 由于采用依赖追踪,默认就是优化状态:你动了多少数据,就触发多少更新,不多也不少。React在复杂的应用里有两个选择:
(1). 手动添加 shouldComponentUpdate 来避免不需要的 vdom re-render。
(2). Components 尽可能都用 pureRenderMixin,然后采用 redux 结构 + Immutable.js;
开发风格的偏好:React 推荐的做法是 JSX + inline style,也就是把 HTML 和 CSS 全都写进 JavaScript 了,即”all in js”;Vue进阶之后推荐的是使用 webpack + vue-loader 的单文件组件格式,即html,css,js写在同一个文件;
使用场景:React配合Redux架构适合超大规模多人协作的复杂项目;Vue则适合小快灵的项目。对于需要对 DOM 进行很多自定义操作的项目,Vue 的灵活性优于 React;
Vue要比React更好上手,具体可能体现在很多人不熟悉React的JSX语法和函数式编程的思想,以及想要发挥出React的最大威力需要学习它一系列生态的缘故;
Vue着重提高开发效率,让前端程序员更快速方便的开发应用。React着重于变革开发思想,提升前端程序员编程的深度与创造力,让前端工程师成为真正的程序员而不是UI的构建者;
gulp和webpack区别
- gulp是一种自动化构建工具,我们可以用它来优化前端的工作流程,比如自动刷新页面、combo、压缩css、js、编译less等等。具体体现为:在gulp的配置文件中书写一个个的task,webpack则是一种打包工具,或者说是一种模块化解决方案,实际上很大一部分人刚开始使用webpack的方式就是通过gulp-webpack这个插件,写好task来使用webpack对前端的一些文件进行打包;
- gulp的处理任务需要自己去写,webpack则有现成的解决方案,只需要在webpack.config.js配置好即可;
防止重复发送Ajax请求
- 用户点击之后按钮disabled;
- 函数节流
- abort掉上一个请求。
事件模型
- 事件捕获阶段(capturing phase)。事件从document一直向下传播到目标元素, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
- 事件处理阶段(target phase)。事件到达目标元素, 触发目标元素的监听函数。
- 事件冒泡阶段(bubbling phase)。事件从目标元素冒泡到document, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
浏览器缓存机制
- Expires策略
Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。
- Cache-Control策略
Cache-Control与Expires的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓读取数据还是重新发请求到服务器取数据。只不过Cache-Control的选择更多,设置更细致,如果同时设置的话,其优先级高于Expires。
以上是设置缓存时间的两种方法。那么当缓存时间过了咋整呢?有人肯定说了,那就再次发起请求啊,这是对的。问题是如果服务器资源并没有更新呢?比如说我有一个jQuery.js
文件已经缓存了,当它的缓存时间到了之后服务器的jQuery.js
文件也没有更新,那实际上我们直接使用本地缓存的文件就可以啊!没必要浪费带宽和时间去重新请求一个新的文件啊!这时候我们就需要再进一步看一下HTTP协议里这几个参数的作用了。
- Last-Modified/If-Modified-Since
首先Last-Modified/If-Modified-Since要配合Cache-Control使用。
- Last-Modified:标示这个响应资源的最后修改时间。web服务器在响应请求时,告诉浏览器资源的最后修改时间(这个参数是和Cache-Control一起过来的)。
If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示请求时间。web服务器收到请求后发现有头If-Modified-Since ,则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的cache。
- ETag/If-None-Match
Etag/If-None-Match也要配合Cache-Control使用。
- Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器觉得)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。
If-None-Match:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match(Etag的值)。web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定返回200或304。
- ETag和Last-Modified
HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:
- Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间
- 如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存
- 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形
Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag**,一致的情况下,才会继续比对Last-Modified**,最后才决定是否返回304。
Ajax的状态值与HTTP状态码
Ajax的状态值
0: (未初始化)还没有调用open()方法。
1: (载入)已经调用open()方法,正在派发请求,send()
方法还未被调用.。
2: (载入完成)send()已经调用,响应头和响应状态已经返回.。
3: (交互)响应体下载中;responseText
中已经获取了部分数据.。
4: (完成)响应内容已经解析完成,用户可以调用。HTTP状态码
200 & OK: 请求成功;
204 & No Content: 请求处理成功,但没有资源可以返回;
206 & Partial Content: 对资源某一部分进行请求(比如对于只加载了一般的图片剩余部分的请求);
301 & Move Permanently: 永久性重定向;
302 & Found: 临时性重定向;
303 & See Other: 请求资源存在另一个URI,应使用get方法请求;
304 & Not Modified: 服务器判断本地缓存未更新,可以直接使用本地的缓存;
307 & Temporary Redirect: 临时重定向;
400 & Bad Request: 请求报文存在语法错误;
401 & Unauthorized: 请求需要通过HTTP认证;
403 & Forbidden: 请求资源被服务器拒绝,访问权限的问题;
404 & Not Found: 服务器上没有请求的资源;
500 & Internal Server Error: 服务器执行请求时出现错误;
502 & Bad Gateway: 错误的网关;
503 & Service Unavailable: 服务器超载或正在维护,无法处理请求;
504 & Gateway timeout: 网关超时;
React-router原理
1.History
- 老浏览器的history: 主要通过hash来实现,对应
createHashHistory
- 高版本浏览器: 通过html5里面的history,对应
createBrowserHistory
- node环境下: 主要存储在memeory里面,对应
createMemoryHistory
内部createHistory
实现:
// 内部的抽象实现
function createHistory(options={}) {
...
return {
listenBefore, // 内部的hook机制,可以在location发生变化前执行某些行为,AOP的实现
listen, // location发生改变时触发回调
transitionTo, // 执行location的改变
push, // 改变location
replace,
go,
goBack,
goForward,
createKey, // 创建location的key,用于唯一标示该location,是随机生成的
createPath,
createHref,
createLocation, // 创建location
}
}
createLocation
方法:
function createLocation() {
return {
pathname, // url的基本路径
search, // 查询字段
hash, // url中的hash值
state, // url对应的state字段
action, // 分为push、replace、pop三种
key // 生成方法为: Math.random().toString(36).substr(2, length)
}
}
三种方法各自执行URL
前进的方式:
createBrowserHistory
: pushState、replaceStatecreateHashHistory
:location.hash=***
location.replace()
createMemoryHistory
: 在内存中进行历史记录的存储
伪代码实现:
// createBrowserHistory(HTML5)中的前进实现
function finishTransition(location) {
...
const historyState = { key };
...
if (location.action === 'PUSH') ) {
window.history.pushState(historyState, null, path);
} else {
window.history.replaceState(historyState, null, path)
}
}
// createHashHistory的内部实现
function finishTransition(location) {
...
if (location.action === 'PUSH') ) {
window.location.hash = path;
} else {
window.location.replace(
window.location.pathname + window.location.search + '#' + path
);
}
}
// createMemoryHistory的内部实现
entries = [];
function finishTransition(location) {
...
switch (location.action) {
case 'PUSH':
entries.push(location);
break;
case 'REPLACE':
entries[current] = location;
break;
}
}
- React-router的基本原理
URL
对应Location
对象,而UI
是由react的 components
来决定的,这样就转变成location
与components
之间的同步问题。
什么是原型链
访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__
这条链向上找,这就是原型链。
在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined
占个空——执行上下文环境。
作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
每一个对象都会在内部链接到另一个对象(该对象的原型对象),该对象有一个原型prototype
,当访问对象的属性或是方法的时候,不仅仅会在原对象上查找,还会顺着原型链在原型对象的原型链上查找,直到查到null
(所有原型链的顶层)为止。原型是JavaScript实现继承的基础,new
关键字做的主要的事情就是将实例对象的__proto__
属性指向原型对象的prototype。
什么是闭包
闭包是javascript支持头等函数的一种方式,它是一个能够引用其内部作用域变量(在本作用域第一次声明的变量)的表达式,这个表达式可以赋值给某个变量,可以作为参数传递给函数,也可以作为一个函数返回值返回。
闭包是函数开始执行的时候被分配的一个栈帧,在函数执行结束返回后仍不会被释放(就好像一个栈帧被分配在堆里而不是栈里!)
闭包的应用:
- 比如写柯里化函数的时候利用闭包,保存参数在内存中;
var currying = function(fun) {
//格式化arguments
var args = Array.prototype.slice.call(arguments, 1);
return function() {
//收集所有的参数在同一个数组中,进行计算
var _args = args.concat(Array.prototype.slice.call(arguments));
return fun.apply(null, _args);
};
}
- 模拟私有变量或是私有方法;
const people = (num) => {
var num = num;
return {
increase: () => {
num++;
},
get: () => {
return num;
}
}
}
const man = people(4);
man.increase();
man.get();
- 避免引用错误
for (var i = 0; i < 4; i++) {
(function(_i) {
setTimeout(function() {
console.log(_i)
}, 1000)
})(i)
}
图片懒加载与预加载
图片懒加载的原理就是暂时不设置图片的
src
属性,而是将图片的url
隐藏起来,比如先写在data-src
里面,等某些事件触发的时候(比如滚动到底部,点击加载图片)再将图片真实的url
放进src
属性里面,从而实现图片的延迟加载图片预加载,是指在一些需要展示大量图片的网站,实现图片的提前加载。从而提升用户体验。常用的方式有两种,一种是隐藏在css的background的url属性里面,一种是通过javascript的Image对象设置实例对象的src属性实现图片的预加载。相关代码如下:
-
- CSS预加载图片方式:
#preload-01 { background: url(http://damonare.cn/image-01.png) no-repeat -9999px -9999px; } #preload-02 { background: url(http://damonare.cn/image-02.png) no-repeat -9999px -9999px; } #preload-03 { background: url(http://damonare.cn/image-03.png) no-repeat -9999px -9999px; }
-
- Javascript预加载图片的方式:
function preloadImg(url) { var img = new Image(); img.src = url; if(img.complete) { //接下来可以使用图片了 //do something here } else { img.onload = function() { //接下来可以使用图片了 //do something here }; } }
类型数组
JavaScript 是一种提供了访问原始二进制数据机制的类似数组的对象。
为了最大的灵活性和效率(mdn这么说的,不明觉厉),JavaScript类型化数组提供了两部分来实现,一种是缓冲(ArrayBuffer),一种是视图(TypedArray和DataView)。
所谓的缓冲其实是一种数据类型,表示一个通用的、固定长度的二进制数据缓冲区。该对象没法直接访问具体的数据,仅仅提供’缓冲’作用,要访问具体的数据,必须通过视图;
所谓的视图分为两种:TypedArray和DataView:
TypedArray:
共包括9种类型的视图,比如Uint8Array
(无符号8位整数)数组视图, Int16Array
(16位整数)数组视图, Float32Array
(32位浮点数)数组视图等等。
数据类型 | 字节长度 | 含义 | 对应的C语言类型 |
---|---|---|---|
Int8 | 1 | 8位带符号整数 | signed char |
Uint8 | 1 | 8位不带符号整数 | unsigned char |
Uint8C | 1 | 8位不带符号整数(自动过滤溢出) | unsigned char |
Int16 | 2 | 16位带符号整数 | short |
Uint16 | 2 | 16位不带符号整数 | unsigned short |
Int32 | 4 | 32位带符号整数 | int |
Uint32 | 4 | 32位不带符号的整数 | unsigned int |
Float32 | 4 | 32位浮点数 | float |
Float64 | 8 | 64位浮点数 | double |
DataView:
DataView提供可以操作缓冲区中任意数据的读写接口。这对操作不同类型数据的场景很有帮助。而不再像TypedArray一样局限于某一种类型数据。
应用:
- FileReader API(readAsArrayBuffer方法)
- XMlHttpRequest的send方法(支持类型数组作为参数)
- Canvas
说明:类型数组并不是支持所有的原生数组的API(比如push和pop就不可用,因为ArrayBuffer给定了字节数,TypedArray视图自然无法调用)。
跨域
跨域的方式有很多种,最常用的是jsonp
主要利用了script
的开放策略:通过script标签引入一个js或者是一个其他后缀形式(如php,jsp等)的文件,此文件返回一个js函数的调用。缺点在于只支持get请求而且存在安全问题,可能会导致CSRF,因为请求的数据来源于其他网站,因为恶意攻击者可以利用这段代码进行请求,获取数据,有可能会泄露用户密码等重要信息。
CORS跨域,关键在于服务器,如果服务器实现了CORS跨域的接口,那么就可以使用ajax(请求路径为绝对路径)进行跨域请求。CORS请求分为两种,一种是简单请求,一种是非简单请求。简单请求是指请求方法在HEAD
,GET
,POST
三者之间并且请求头信息局限在
- Accept
- Accept-Language
Content-Language
Content-Type:只限于三个值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
非简单请求请求头:
(1)Access-Control-Request-Method
该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法
(2)Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段
执行简单请求的时候,浏览器会在请求头信息增加origin
字段,服务器据此来判断请求域名是否在许可范围之内,来决定是否返回Access-Control-Allow-Origin
字段。响应头有以下几种:
(1)Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin
字段的值,要么是一个*
,表示接受任意域名的请求。
(2)Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true
,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true
,如果服务器不要浏览器发送Cookie,删除该字段即可。
(3)Access-Control-Expose-Headers
该字段可选。CORS请求时,XMLHttpRequest
对象的getResponseHeader()
方法只能拿到6个基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。如果想拿到其他字段,就必须在Access-Control-Expose-Headers
里面指定。
(4)Access-Control-Max-Age
Access-Control-Max-Age
首部字段指明了预检请求的响应的有效时间。
(5)Access-Control-Allow-Methods
Access-Control-Allow-Methods
首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。
(6)Access-Control-Allow-Headers
Access-Control-Allow-Headers
首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段。
其他方法:document.domin
(IE6,7配合iframe;IE6,7发送POST跨域请求),html5的postMessage
,window.name
等
函数节流和函数防抖
函数节流让指函数有规律的进行调用,应用场景:window.resize,游戏中子弹发射(1s只能发射一颗子弹)等;
函数防抖让函数在”调用”之后的一段时间后生效,应用场景:输入框(例:在用户停止输入的500ms后再处理用户数据)。
//函数节流
/*
* @params {Function} fun 调用函数
* @params {Number} delay 延迟时间
*/
const throttle = (fun, delay, ...rest) => {
let last = null;
return () => {
const now = + new Date();
if (now - last > delay) {
fun(rest);
last = now;
}
}
}
//实例
const throttleExample = throttle(() => console.log(1), 1000);
//调用
throttleExample();
throttleExample();
throttleExample();
//函数防抖
const debouce = (fun, delay, ...rest) => {
let timer = null;
return () => {
clearTimeout(timer);
timer = setTimeout(() => {
fun(rest);
}, delay);
}
}
//实例
const debouceExample = debouce(() => console.log(1), 1000);
//调用
debouceExample();
debouceExample();
debouceExample();
百度实习遇到的问题和解决方案
sort
API的问题,场景:后端返回的某一段数据经过前端sort方法排序后,顺序乱掉了。深入研究得知:sort方法底层算法是这样的:22以内是插入排序,22以上是快速排序。该场景下基准选择的是第一个数据,导致第一次快排结束后数据的顺序乱掉了。解决方法:直接提取,因为后端返回的数据是正确的。react-router
,问题:onEnter钩子函数的问题,传入第三个参数callback,官方文档说callback得异步执行,但必须在异步调用一次后在主线程再调用一次才会生效,看源码未解。最后放弃了传入callback改为同步执行解决。给react-router提了issue,未得到回应。
快速排序
- 从数列中挑出一个元素,称为”基准”(pivot),
- 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition) 操作。
- 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
时间复杂度平均情况:O(n\log n) 最快:O(n^{2}) 空间复杂度: O(\log n)
var quickSort = function(arr) {
console.time('2.快速排序耗时');
if (arr.length <= 1) { return arr; }
var pivot = arr.splice(0, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
console.timeEnd('2.快速排序耗时');
return quickSort(left).concat([pivot], quickSort(right));
};
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(quickSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
模块加载器加载原理
id即路径原则
通常我们的入口是这样的: require( [ ‘a’, ‘b’ ], callback ) 。这里的 ‘a’、’b’ 都是 ModuleId。通过 id 和路径的对应原则,加载器才能知道需要加载的 js 的路径。在这个例子里,就是 baseUrl + ‘a.js’ 和 baseUrl + ‘b.js’。但 id 和 path 的对应关系并不是永远那么简单,比如在 AMD 规范里就可以通过配置 Paths 来给特定的 id 指配 path。createElement(‘script’) & appendChild
知道路径之后,就需要去请求。一般是通过 createElement(‘script’) & appendChild 去请求。这个大家都知道,不多说。有时候有的加载器也会通过 AJAX 去请求脚本内容。document.currentScript
a.js 里可能是 define( id, factory ) 或者是 define( factory ),后者被称为匿名模块。那么当 define(factory) 被执行的时候,我们怎么知道当前被定义的是哪个模块呢,具体地说,这个匿名模块的实际模块 id 是什么? 答案是通过 document.currentScript 获取当前执行的
Module.prototype.load = function () {
var deps = this.getDeps();
for (var i = 0; i < deps.length; i++) {
var m = deps[i];
if (m.state < STATUS.LOADED) {
m.load();
}
}
this.state = STATUS.LOADED;
}
上面的代码只是表达一个意思,实际上 load 方法很可能是异步的,所以递归的返回要特殊处理下。实现一个可用的加载器并没有那么简单,比如你要处理循环依赖,还有各种各样的牵一发动全身的细节。但要说原理,大概就是这么几条。个人觉得,比起照着规范实现一个加载器,更加吸引人的是 AMD 或者 CommonJS 这些规范的完善和背后的设计思路。
—-来自JS模块加载器加载原理
AMD和CMD的区别
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
- 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
- CMD 推崇依赖就近,AMD 推崇依赖前置。
- AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。
Web worker
Web Workers是一种机制,通过它可以使一个脚本操作在与Web应用程序的主执行线程分离的后台线程中运行。这样做的优点是可以在单独的线程中执行繁琐的处理,让主(通常是UI)线程运行而不被阻塞/减慢。
Web Worker 规范中定义了两类工作线程,分别是专用线程Dedicated Worker和共享线程 Shared Worker,其中,Dedicated Worker只能为一个页面所使用,而Shared Worker则可以被多个页面所共享。
——来自深入理解web worker
JavaScript内存泄露的原因以及如何去手动释放内存
易出现泄露的场景
- XMLHttpRequest 泄漏发生在IE7-8,释放方法,将XMLHttpRequest实例对象设置为Null;
- DOM&BOM等COM对象循环绑定 泄漏发生在IE6-8,释放方法,切断循环引用,将对对象的应用设置为Null;
- 定时器(严格上说不能算是泄露,是被闭包持有了,是正常的表现),对于闭包中无用的变量可以使用delete操作符进行释放;
JavaScript垃圾回收机制
- 引用计数
此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。
限制:无法处理循环引用。在下面的例子中,两个对象被创建,并互相引用,形成了一个循环。它们被调用之后不会离开函数作用域,所以它们已经没有用了,可以被回收了。然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。
- 标记清除
当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。
垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。
项目遇到的问题以及如何解决的
- Canvas和图像的问题,当图像写入canvas之后只能写不能读,canvas对象的toDataURL等API都不能用了,原因是同源策略存在的缘故。解决方案:将图片放在支持CORS的服务器上,设置
img
属性值crossorigin
为anonymous
使canvas可读;
柯里化函数
所谓的柯里化函数简单的说就是将本来接受多个参数的函数变为只接受一个参数的函数。柯里化函数的模板和实例如下:
var currying = function(fun) {
//格式化arguments
var args = Array.prototype.slice.call(arguments, 1);
return function() {
//收集所有的参数在同一个数组中,进行计算
var _args = args.concat(Array.prototype.slice.call(arguments));
return fun.apply(null, _args);
};
}
var add = currying(function() {
var args = Array.prototype.slice.call(arguments);
return args.reduce(function(a, b) {
return a + b;
});
})
add(1, 2, 4)
/*
* 经典面试题
* 函数参数不定回调函数数目不定
* 编写函数实现:
* add(1,2,3,4,5)==15
* add(1,2)(3,4)(5)==15
*/
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = [].slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var adder = function () {
var _adder = function() {
[].push.apply(_args, [].slice.call(arguments));
return _adder;
};
// 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
return adder.apply(null, _args);
}
// 输出结果,可自由组合的参数
console.log(add(1, 2, 3, 4, 5)); // 15
console.log(add(1, 2, 3, 4)(5)); // 15
console.log(add(1)(2)(3)(4)(5)); // 15
Less常用特性
- 变量(@color = #fff)
- 混合(Mixin)
- 内置函数(颜色,字符串,类型判断,数学)
- 循环
- 嵌套
- 运算
- 导入(@import)
ES6常用特性
- 变量定义(let和const,可变与不可变,const定义对象的特殊情况)
- 解构赋值
- 模板字符串
- 数组新API(例:Array.from(),entries(),values(),keys())
- 箭头函数(rest参数,扩展运算符,::绑定this)
- Set和Map数据结构(set实例成员值唯一存储key值,map实例存储键值对(key-value))
- Promise对象(前端异步解决方案进化史,generator函数,async函数)
- Class语法糖(super关键字)
React的生命周期
组件的生命周期可谓是老生常谈了,React的生命周期可分为三个状态:mounting
、Updating
、Unmounting
。整个React组件可以看做是一个有限状态机,即通过状态来渲染页面
Mounting
当组件实例创建之后插入DOM中下列方法会被调用:
Updating
当父组件的props改变或是组件自身state改变都有可能导致组件的re-render(重新渲染):
Unmounting
当组件从DOM中移除的时候调用该方法:
—来自官网React.Component
详解生命周期文章:React 源码剖析系列 - 生命周期的管理艺术
React中setState的原理
题目:
import React from 'react'
class App extends React.Component {
constructor() {
super();
this.state = {
value: 0
}
}
componentDidMount() {
this.setState({value: this.state.value + 1});
console.log(this.state.value);
this.setState({value: this.state.value + 1});
console.log(this.state.value);
this.setState({value: this.state.value + 1});
console.log(this.state.value);
setTimeout(() => {
this.setState({value: this.state.value + 1});
console.log(this.state.value);
this.setState({value: this.state.value + 1});
console.log(this.state.value);
}, 0)
}
}
答案: 0、0、0、2、3;
分析:
当setState
方法调用的时候React
就会重新调用render
方法来重新渲染组件;setState
通过一个队列来更新state
,当调用setState
方法的时候会将需要更新的state放入这个状态队列中,这个队列会高效的批量更新state
;
源码地址:enqueueUpdate
function enqueueUpdate(component) {
ensureInjected();
//判断是否处于批量更新模式
if (!batchingStrategy.isBatchingUpdates) {
//关键!下面的代码片段是这个方法的源码
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
//如果处于批量更新模式,则将这个组件保存在dirtyComponents
dirtyComponents.push(component);
}
源码地址:ReactDefaultBatchingStrategy
//batchingStrategy对象
var ReactDefaultBatchingStrategy = {
//注意默认为false
isBatchingUpdates: false,
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
callback(a, b, c, d, e);
} else {
//关键!!!事务的理解
transaction.perform(callback, null, a, b, c, d, e);
}
},
};
源码地址:Transaction
如图:事务会将所需要执行的方法(图中的anyMethod)使用wrapper
封装起来,再通过perform
方法执行该方法,但在perform
执行之前会先执行所有wrapper
中的initialize
方法,perform
方法执行结束后,再执行所有的close
方法;
var Transaction = require('./Transaction');
// 我们自己定义的
var MyTransaction = function() {
//do something
};
Object.assign(MyTransaction.prototype, Transaction.Mixin, {
//需要自定义一个getTransactionWrappers对象,获取所有需要封装的initialize方法和close方法
getTransactionWrappers: function() {
return [{
initialize: function() {
console.log('before method perform');
},
close: function() {
console.log('after method perform');
}
}];
};
});
//实例化一个transaction
var transaction = new MyTransaction();
//需要调用的方法
var testMethod = function() {
console.log('test');
}
transaction.perform(testMethod);
//before method perform
//test
//after method perform
理解题目的关键是,整个组件渲染到DOM中的过程就已经处于一次大的事务中了,因此在componentDidMount
方法中调用setState
的时候ReactDefaultBatchingStrategy.isBatchingUpdates = true;
这句代码已经执行过了,所以setState
的结果并没有立即生效,而是扔进了dirtyComponent
;因此执行三次setState的结果this.state.value的值依然是0,而setTimeout中的两次setState由于没有调用过batchedUpdates
方法(isBatchingUpdates
默认为false
),所以setState
方法立即生效,第二次setSState
同理
XSS与CSRF介绍
XSS是一种跨站脚本攻击,是属于代码注入的一种,攻击者通过将代码注入网页中,其他用户看到会受到影响(代码内容有请求外部服务器);
CSRF是一种跨站请求伪造,冒充用户发起请求,完成一些违背用户请求的行为(删帖,改密码,发邮件,发帖等);
XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
XSS 防御方法举例:
- 对一些关键字和特殊字符进行过滤(<>,,script等),或对用户输入内容进行URL编码(encodeURIComponent);
- Cookie不要存放用户名和密码,对cookie信息进行MD5等算法散列存放,必要时可以将IP和cookie绑定;
CSRF防御方法举例:
- 检查Referer字段
- 添加校验token
new关键字的过程
- 创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
- 属性和方法被加入到 this 引用的对象中。
- 新创建的对象由 this 所引用,并且最后隐式的返回 this 。
伪代码如下:
var obj = {};
obj.__proto__ = Base.prototype;//Base为构造函数
Base.call(obj);
return obj
外边距合并
- 相邻的兄弟姐妹元素
<p style="margin-bottom: 30px;">这个段落的下外边距被合并...</p>
<p style="margin-top: 20px;">...这个段落的上外边距被合并。</p>
如上,两个P元素之间的间隔并不会是想象中的50px,而会是两者之间的最大值30px;
- 块级父元素与其第一个/最后一个子元素
块级父元素的 margin-bottom/margin-top与它的最后一个子元素的margin-bottom/margin-top之间没有父元素的 border
、padding
、inline content、height、min-height、 max-height 分隔时,就会发生下/上外边距合并现象
- 空块元素
<p style="margin-bottom: 0px;">这个段落的和下面段落的距离将为20px</p>
<div style="margin-top: 20px; margin-bottom: 20px;"></div>
<p style="margin-top: 0px;">这个段落的和上面段落的距离将为20px</p>
浮动 及 绝对定位元素外边距不会合并。—来自MDN
BFC
浮动元素和绝对定位元素,非块级盒子的块级容器(例如 inline-blocks, table-cells, 和 table-captions),以及overflow值不为“visiable”的块级盒子,都会为他们的内容创建新的BFC(块级格式上下文)。本身根元素就处在一个大的BFC中,BFC中元素竖直排列,IFC(行内格式上文)中的元素横向排列。
块格式化上下文由以下之一创建:
- 根元素或其它包含它的元素
- 浮动 (元素的
float
不是none
) - 绝对定位的元素 (元素具有
position
为absolute
或fixed
) - 内联块 inline-blocks (元素具有
display
: inline-block) - 表格单元格 (元素具有
display
: table-cell,HTML表格单元格默认属性) - 表格标题 (元素具有
display
: table-caption, HTML表格标题默认属性) - 块元素具有
overflow
,且值不是visible
display
: flow-root;—来自MDN
用处:
- 设置父元素为BFC清除子元素浮动;
- 解决上面的margin合并问题;
- 用于布局
CSS3动画
关键属性:animation
和transition
(过渡),该属性现在已经被绝大多数浏览器支持,使用频率也越来越高。顾名思义,该属性允许我们使用css来写动画,所谓写:
// HTML
<div id="animate">233</div>
// CSS
#animate {
width: 200px;
height: 200px;
background: #233;
animation: anim 5s ease;
}
@keyframes anim {
from {
width: 100px
}
to {
width: 200px;
}
}
以上就是一个简单的动画,核心是ease运动曲线(本质是贝塞尔曲线),这个运用比较广泛,svg,canvas等都有用到它;关于贝塞尔曲线(核心四个点:起点,重点和两个中间点)有时间再深究。
CSS原生变量
变量定义形式:--
,我想大概应为@,$都被用掉了吧(less,scss无辜躺枪),变量对大小写敏感,比如–color和–Color是两个变量;
var():var函数用来都读取变量;
示例:
:root {
--color: #333;
}
body {
background: var(--color);
}
移动端click事件300ms延迟问题
追溯到早期苹果厂商给iPhone设置的双击缩放功能,浏览器需要300ms时间判断用户是否会有下一次点击(以此触发缩放或是双击666等功能)。
解决方案:
- 禁用缩放
<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">
- 更改默认的视口宽度(width=device-width)
<meta name="viewport" content="width=device-width">
- 指针事件 (Pointer Events)
根据规范,touch-action
属性决定 “是否触摸操作会触发用户代理的默认行为。这包括但不限于双指缩放等行为”。
域名发散和域名收敛
域名发散:所谓域名发散是为了突破浏览器的并发限制。因此遵循这么一条定律:
http 静态资源采用多个子域名
比如:如果有大量图片会通过多个域名进行请求。
浏览器同一域名并发数存在的原因:
- 减轻服务器压力,防止服务器崩溃。
- 出于安全的考虑,防止DDOS攻击。因为一般DDOS攻击就是通过合理的请求加大服务器压力从而搞垮服务器,有了这个并发限制,DDOS起码没有那么简单了。
域名收敛:这个是和域名发散相反的(手动滑稽),发散是说将资源分散为多个域名进行请求,以避开浏览器的并发限制。而域名收敛则是说将资源放在同一个域名下进行请求。那么实际上这是两种应用场景的解决方案罢了。域名发散是为了解决PC端浏览器并发请求限制。而域名收敛则是针对的移动端的(2G,3G网络环境不容忽视)。
域名收敛的原因很简单,减少不必要的域名解析,充分利用缓存,从而加快页面打开的速度。
关于域名收敛可以看这篇:无线性能优化:域名收敛
textarea和input的区别
在HTML中有两种方式表达文本框,一个是input元素的单行文本框,一种是textarea的多行文本框。
input元素
- 一定要指定type的值为text;
- 通过size属性指定显示字符的长度,value属性指定初始值,Maxlength属性指定文本框可以输入的最长的长度;
- 表单有两个type不是submit的input不会自动提交(回车自动提交),表单只有一个input会自动提交,有一个type为submit的按钮或是input会自动提交
textarea元素
- 使用textarea标签对
- 内容放在textarea标签对中
- 使用row、col指定大小
– 来自博客园HTML中input和textarea的区别
TCP和UDP的区别
- TCP面向连接(确认有创建三方交握,连接已创建才作传输。)
UDP不是面向连接的,UDP传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说UDP是无连接的、不可靠的一种数据传输协议。 - UDP传输速率更高,因为不必确认收发数据,实时性更好
- TCP数据是有序的,以什么顺序发送的数据,接收时同样会按照此顺序;UDP是无序的,发出(1,2,3),有可能按照(1,3,2)的顺序收到。应用程序必须自己做分组排序。
TCP不会丢包的机制实现
TCP是通过序列号确认号和计时器来检测丢包和保证数据顺序的。使用校验和来检测报文段的错误。
- 发送方首先发送第一个包含序列号为1(可变化)和1460字节数据的TCP报文段给接收方。接收方以一个没有数据的TCP报文段来回复(只含报头),用确认号1461来表示已完全收到并请求下一个报文段。
- 发送方然后发送第二个包含序列号为1461和1460字节数据的TCP报文段给接收方。正常情况下,接收方以一个没有数据的TCP报文段来回复,用确认号2921(1461+1460)来表示已完全收到并请求下一个报文段。发送接收这样继续下去。
- 然而当这些数据包都是相连的情况下,接收方没有必要每一次都回应。比如,他收到第1到5条TCP报文段,只需回应第五条就行了。在例子中第3条TCP报文段被丢失了,所以尽管他收到了第4和5条,然而他只能回应第2条。
- 发送方在发送了第三条以后,没能收到回应,因此当时钟(timer)过时(expire)时,他重发第三条。(每次发送者发送一条TCP报文段后,都会再次启动一次时钟:RTT)。
- 这次第三条被成功接收,接收方可以直接确认第5条,因为4,5两条已收到。
TCP的16位的校验和(checksum)的计算和检验过程如下:发送者将TCP报文段的头部和数据部分的和计算出来,再对其求反码(一的补数),就得到了校验和,然后将结果装入报文中传输。(这里用反码和的原因是这种方法的循环进位使校验和可以在16位、32位、64位等情况下的计算结果再叠加后相同)接收者在收到报文后再按相同的算法计算一次校验和。
Cookie和Session区别
作者:轩辕志远 链接:https://www.zhihu.com/question/19786827/answer/28752144
由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session.典型的场景比如购物车,当你点击下单按钮时,由于HTTP协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个唯一标识。在服务端保存Session的方法很多,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,一般会有专门的Session服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放 Session。
思考一下服务端如何识别特定的客户?这个时候Cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在 Cookie 里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。
- Cookie其实还可以用在一些方便用户的场景下,设想你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以写到Cookie里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户名给填了,能够方便一下用户。这也是Cookie名称的由来,给用户的一点甜头。
所以,总结一下:
Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;
Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。
React中,因为异步操作的关系,组件销毁后调用了setState(),报警告,怎么解决?
正确的做法是不要在请求callback里setState,用不会被卸载的组件请求然后用props分发下来数据。
这也是为什么Redux或者Mobx流行的原因,只用React基本就要踩到这种坑,因为除了最顶层的组件,有几个组件是真的不会被卸载的呢?
如果实在没办法临时解决,只能加个this.isUnmount,在componentWillUnmount设置为true,然后在fetch callback里判断如果this.isUnmount不为true的时候再setState。
千分符
function toThousands(num) {
var num = (num || 0).toString(), result = '';
while (num.length > 3) {
result = ',' + num.slice(-3) + result;
num = num.slice(0, num.length - 3);
}
if (num) { result = num + result; }
return result;
}
浏览器高层架构
- 用户界面 用户界面包含了地址栏,前进后退按钮,书签菜单等等,除了请求页面之外所有你看到的内容都是用户界面的一部分
- 浏览器引擎 浏览器引擎负责让 UI 和渲染引擎协调工作
- 渲染引擎 渲染引擎负责展示请求内容。如果请求的内容是 HTML,渲染引擎会解析 HTML 和 CSS,然后将内容展示在屏幕上
- 网络组件 网络组件负责网络调用,例如 HTTP 请求等,使用一个平台无关接口,下层是针对不同平台的具体实现
- UI后端 UI 后端用于绘制基本 UI 组件,例如下拉列表框和窗口。UI 后端暴露一个统一的平台无关的接口,下层使用操作系统的 UI 方法实现
- Javascript 引擎 Javascript 引擎用于解析和执行 Javascript 代码
- 数据存储 数据存储组件是一个持久层。浏览器可能需要在本地存储各种各样的数据,例如 Cookie 等。浏览器也需要支持诸如 localStorage,IndexedDB,WebSQL 和 FileSystem 之类的存储机制
delete操作符注意点
- 如果你删除的属性在对象上不存在,那么delete将不会起作用,但仍会返回true
- 如果
delete
操作符删除成功,则被删除的属性将从所属的对象上彻底消失。然后,如果该对象的原型链上有一个同名属性,则该对象会从原型链上继承该同名属性。(也就是说delete操作只会在自身的属性上起作用) - 任何使用 var 声明的属性不能从全局作用域或函数的作用域中删除。
- 这样的话,delete操作不能删除任何在全局作用域中的函数(无论这个函数是来自于函数声明或函数表达式)
- 除了在全局作用域中的函数不能被删除,在对象(object)中的函数是能够用delete操作删除的。
- 任何用
let
或const
声明的属性不能够从它被声明的作用域中删除。 - 不可设置的(Non-configurable)属性不能被移除。这意味着像
Math
,Array
,Object
内置对象的属性以及使用Object.defineProperty()
方法设置为不可设置的属性不能被删除。
get和post区别
- GET后退按钮/刷新无害,POST数据会被重新提交(浏览器应该告知用户数据会被重新提交)。
- GET书签可收藏,POST为书签不可收藏。
- GET能被缓存,POST不能缓存 。
- GET编码类型application/x-www-form-url,POST编码类型encodedapplication/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。
- GET历史参数保留在浏览器历史中。POST参数不会保存在浏览器历史中。
- GET对数据长度有限制,当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。POST无限制。
- GET只允许 ASCII 字符。POST没有限制。也允许二进制数据。
- 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时绝不要使用 GET !POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。
- GET的数据在 URL 中对所有人都是可见的。POST的数据不会显示在 URL 中。
前端需要注意的SEO
- 合理的title、description、keywords:搜索对着三项的权重逐个减小,title值强调重点即可,重要关键词出现不要超过2次,而且要靠前,不同页面title要有所不同;description把页面内容高度概括,长度合适,不可过分堆砌关键词,不同页面description有所不同;keywords列举出重要关键词即可
- 语义化的HTML代码,符合W3C规范:语义化代码让搜索引擎容易理解网页
- 重要内容HTML代码放在最前:搜索引擎抓取HTML顺序是从上到下,有的搜索引擎对抓取长度有限制,保证重要内容一定会被抓取
- 重要内容不要用js输出:爬虫不会执行js获取内容
- 少用iframe:搜索引擎不会抓取iframe中的内容
- 非装饰性图片必须加alt
- 提高网站速度:网站速度是搜索引擎排序的一个重要指标