我的前端进阶之路

21,045 阅读22分钟

前言

总括: 包含这三个月来碰到的一些觉得比较好的面试题,三个月没怎么写博客着实有些手痒,哈哈哈。7000余字,不成敬意2333

烈火试真金,逆境试强者

正文

React和Vue对比

相同点:

  1. 数据驱动视图,提供响应式的视图组件
  2. 都有Virtual DOM,组件化开发,通过props参数进行父子组件数据的传递,都实现webComponents规范
  3. 数据流动单向
  4. 都支持服务端渲染
  5. 都有支持native的方案,React的React native,Vue的weex

不同点:

  1. 社区:React社区还是要比vue大很多;

  2. 开发模式:React在view层侵入性还是要比Vue大很多的,React严格上只针对MVC的view层,Vue则是MVVM模式的一种实现;

  3. 数据绑定:Vue有实现了双向数据绑定,React数据流动是单向的

  4. 数据渲染:对于大规模数据渲染,React要比Vue更快,渲染机制启动时候要做的工作比较多;

  5. 数据更新方面:Vue 由于采用依赖追踪,默认就是优化状态:你动了多少数据,就触发多少更新,不多也不少。React在复杂的应用里有两个选择:

    (1). 手动添加 shouldComponentUpdate 来避免不需要的 vdom re-render。 (2).Components 尽可能都用 pureRenderMixin,然后采用 redux 结构 + Immutable.js;

  6. 开发风格的偏好:React 推荐的做法是 JSX + inline style,也就是把 HTML 和 CSS 全都写进 JavaScript 了,即"all in js";Vue进阶之后推荐的是使用 webpack + vue-loader 的单文件组件格式,即html,css,js写在同一个文件;

  7. 使用场景:React配合Redux架构适合超大规模多人协作的复杂项目;Vue则适合小快灵的项目。对于需要对 DOM 进行很多自定义操作的项目,Vue 的灵活性优于 React;

  8. Vue要比React更好上手,具体可能体现在很多人不熟悉React的JSX语法和函数式编程的思想,以及想要发挥出React的最大威力需要学习它一系列生态的缘故;

  9. Vue着重提高开发效率,让前端程序员更快速方便的开发应用。React着重于变革开发思想,提升前端程序员编程的深度与创造力,让前端工程师成为真正的程序员而不是UI的构建者;

gulp和webpack区别

  1. gulp是一种工具,我们可以用它来优化前端的工作流程,比如自动刷新页面、combo、压缩css、js、编译less等等。具体体现为:在gulp的配置文件中书写一个个的task,webpack则是一种打包工具,或者说是一种模块化解决方案,实际上很大一部分人刚开始使用webpack的方式就是通过gulp-webpack这个插件,写好task来使用webpack对前端的一些文件进行打包;
  2. gulp的处理任务需要自己去写,webpack则有现成的解决方案,只需要在webpack.config.js配置好即可;

防止重复发送Ajax请求

  1. 用户点击之后按钮disabled;
  2. 函数节流
  3. abort掉上一个请求。

事件模型

  • 事件捕获阶段(capturing phase)。事件从document一直向下传播到目标元素, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
  • 事件处理阶段(target phase)。事件到达目标元素, 触发目标元素的监听函数。
  • 事件冒泡阶段(bubbling phase)。事件从目标元素冒泡到document, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。

浏览器缓存机制

  1. Expires策略

Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。

  1. Cache-Control策略

Cache-Control与Expires的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓读取数据还是重新发请求到服务器取数据。只不过Cache-Control的选择更多,设置更细致,如果同时设置的话,其优先级高于Expires

以上是设置缓存时间的两种方法。那么当缓存时间过了咋整呢?有人肯定说了,那就再次发起请求啊,这是对的。问题是如果服务器资源并没有更新呢?比如说我有一个jQuery.js文件已经缓存了,当它的缓存时间到了之后服务器的jQuery.js文件也没有更新,那实际上我们直接使用本地缓存的文件就可以啊!没必要浪费带宽和时间去重新请求一个新的文件啊!这时候我们就需要再进一步看一下HTTP协议里这几个参数的作用了。

  1. 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。
  1. 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。
  1. 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、replaceState
  • createHashHistory: 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;
  }
}
  1. React-router的基本原理

URL对应Location对象,而UI是由react的 components来决定的,这样就转变成locationcomponents之间的同步问题。

什么是原型链

每一个对象都会在内部链接到另一个对象(该对象的原型对象),该对象有一个原型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属性实现图片的预加载。相关代码如下:

    1. CSS预加载图片方式:
    #preload-01 { background: url(http://domain.tld/image-01.png) no-repeat -9999px -9999px; }  
    #preload-02 { background: url(http://domain.tld/image-02.png) no-repeat -9999px -9999px; }  
    #preload-03 { background: url(http://domain.tld/image-03.png) no-repeat -9999px -9999px; }
    
    1. Javascript预加载图片的方式:
    function preloadImg(url) {
        var img = new Image();
        img.src = url;
        if(img.complete) {
            //接下来可以使用图片了
            //do something here
        } else {
            img.onload = function() {
                //接下来可以使用图片了
                //do something here
            };
        }
    }
    

跨域

跨域的方式有很多种,最常用的是jsonp主要利用了script的开放策略:通过script标签引入一个js或者是一个其他后缀形式(如php,jsp等)的文件,此文件返回一个js函数的调用。缺点在于只支持get请求而且存在安全问题。

CORS跨域,关键在于服务器,如果服务器实现了CORS跨域的接口,那么就可以使用ajax(请求路径为绝对路径)进行跨域请求。CORS请求分为两种,一种是简单请求,一种是非简单请求。简单请求是指请求方法在HEAD,GET,POST三者之间并且请求头信息局限在

  • Accept

  • Accept-Language

  • Content-Language

  • Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/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-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在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,html5的postMessage,window.name

函数节流和函数防抖

函数节流让指函数有规律的进行调用,应用场景:window.resize,游戏中子弹发射(1s只能发射一颗子弹)等;

函数防抖让函数在"调用''之后的一段时间后生效,应用场景:输入框(例:在用户停止输入的500ms后再处理用户数据)。

//函数节流
/*
* @params {Function} fun 调用函数
* @params {delay} number 延迟时间
*/
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();

快速排序

  1. 从数列中挑出一个元素,称为"基准"(pivot),
  2. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为**分区(partition)**操作。
  3. 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。

时间复杂度平均情况:O(n\log n) 最快:O(n^{2}) 空间复杂度: O(\log n)

var quickSort = function(arr) {
    console.time('2.快速排序耗时');
&emsp;&emsp;if (arr.length <= 1) { return arr; }
&emsp;&emsp;var pivot = arr.splice(0, 1)[0];
&emsp;&emsp;var left = [];
&emsp;&emsp;var right = [];
&emsp;&emsp;for (var i = 0; i < arr.length; i++){
&emsp;&emsp;&emsp;&emsp;if (arr[i] < pivot) {
&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;left.push(arr[i]);
&emsp;&emsp;&emsp;&emsp;} else {
&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;right.push(arr[i]);
&emsp;&emsp;&emsp;&emsp;}
&emsp;&emsp;}
console.timeEnd('2.快速排序耗时');
&emsp;&emsp;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]

AMD和CMD的区别

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。 CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。

  1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.
  2. CMD 推崇依赖就近,AMD 推崇依赖前置
  3. AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹

JavaScript内存泄露的原因以及如何去手动释放内存

易出现泄露的场景

  • XMLHttpRequest 泄漏发生在IE7-8,释放方法,将XMLHttpRequest实例对象设置为Null;
  • DOM&BOM等COM对象循环绑定 泄漏发生在IE6-8,释放方法,切断循环引用,将对对象的应用设置为Null;
  • 定时器(严格上说不能算是泄露,是被闭包持有了,是正常的表现),对于闭包中无用的变量可以使用delete操作符进行释放;

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);
  	};
}
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中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;

setState简化调用栈

源码地址: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是一种跨站请求伪造,冒充用户发起请求,完成一些违背用户请求的行为(删帖,改密码,发邮件,发帖等)

防御方法举例:

  1. 对一些关键字和特殊字符进行过滤(<>,?,script等),或对用户输入内容进行URL编码(encodeURIComponent);
  2. Cookie不要存放用户名和密码,对cookie信息进行MD5等算法散列存放,必要时可以将IP和cookie绑定;

后记

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/2/3/1700919fb9a285de~tplv-t2oaga2asx-image.image