常考算法

121 阅读18分钟

算法

反转链表

var reverseList = function(head) {
    if (!head || !head.next) return head;
    let pre = null;
    let current = head;
    let next;
    while(current) {
        next = current.next;
        current.next = pre;
        pre = current;
        current = next;
    }
    return pre;
};

归并排序

归并排序的平均时间复杂度为 O(nlogn) ,最坏时间复杂度为 O(nlogn) ,空间复杂度为 O(n) ,是稳定排序。

function mergeSort(array) {
  let length = array.length;

  // 如果不是数组或者数组长度小于等于0,直接返回,不需要排序 
  if (!Array.isArray(array) || length === 0) return;

  if (length === 1) {
    return array;
  }

  let mid = parseInt(length >> 1), // 找到中间索引值
    left = array.slice(0, mid), // 截取左半部分
    right = array.slice(mid, length); // 截取右半部分

  return merge(mergeSort(left), mergeSort(right)); // 递归分解后,进行排序合并
}

function merge(leftArray, rightArray) {
  let result = [],
    leftLength = leftArray.length,
    rightLength = rightArray.length,
    il = 0,
    ir = 0;

  // 左右两个数组的元素依次比较,将较小的元素加入结果数组中,直到其中一个数组的元素全部加入完则停止
  while (il < leftLength && ir < rightLength) {
    if (leftArray[il] < rightArray[ir]) {
      result.push(leftArray[il++]);
    } else {
      result.push(rightArray[ir++]);
    }
  }

  // 如果是左边数组还有剩余,则把剩余的元素全部加入到结果数组中。
  while (il < leftLength) {
    result.push(leftArray[il++]);
  }

  // 如果是右边数组还有剩余,则把剩余的元素全部加入到结果数组中。
  while (ir < rightLength) {
    result.push(rightArray[ir++]);
  }

  return result;
}

快速排序

快速排序的平均时间复杂度为 O(nlogn) ,最坏时间复杂度为 O(n²) ,空间复杂度为 O(logn) ,不是稳定排序。

var sortArray = function(nums) {
    // 快速排序
    function quickSort(start, end, arr) {
        if (start < end) {
            let mid = sort(start, end, arr);
            // 注意,一定要是 start mid , mid+1 end 这种组合
            // 否则当首位最大的时候(mid返回0),将会无限循环
            quickSort(start, mid, arr);
            quickSort(mid+1, end, arr);
        }
        return arr;
    }

    function sort(start, end, arr) {
        // 取基准值
        let base = arr[start];
        let low = start;
        let high = end;
        while(low !== high) {
            // 从后往前,寻找比基准值小的值,赋给low位置(也就是取base值的位置)
            while(arr[high] >= base && high > low) {
                high--;
            }
            arr[low] = arr[high];
            // 从前往后,寻找比基准值大的值,赋给high位置
            while(arr[low] <= base && high > low) {
                low++;
            }
            arr[high] = arr[low];
        }
        arr[low] = base;
        return low;
    }
    return quickSort(0, nums.length - 1, nums);
};

括号匹配

var isValid = function (s) {
  let valid = true;
  const stack = [];
  const mapper = {
    "{": "}",
    "[": "]",
    "(": ")",
  };

  for (let i in s) {
    const v = s[i];
    if (["(", "[", "{"].indexOf(v) > -1) {
      stack.push(v);
    } else {
      const peak = stack.pop();
      if (v !== mapper[peak]) {
        return false;
      }
    }
  }

  if (stack.length > 0) return false;

  return valid;
};

无重复字符的最长子串

var lengthOfLongestSubstring = function(s) {
    var counter = new Map()
    let max = 0
    for (let i=0, j=0; j<s.length; j++){
        if (counter.has(s[j])){
             i = Math.max(counter.get(s[j]) + 1, i)
        }
        max = Math.max(max, j - i + 1)
        counter.set(s[j], j)
    }
    return max
};
瓶子君

斐波那契数列

var fib = function(n) {
    const aFi = new Array(n+1);
    aFi[0] = 0; aFi[1] = 1;
    for(let i=2; i<= n; i++){
        aFi[i] = aFi[i-1] + aFi[i-2];
    }
    return aFi[n];
};

js

浏览器缓存

image.png

webpack

我当时使用 webpack 的一个最主要原因是为了简化页面依赖的管理,并且通过将其打包为一个文件来降低页面加载时请求的资源数。

我认为 webpack 的主要原理是,它将所有的资源都看成是一个模块,并且把页面逻辑当作一个整体,通过一个给定的入口文件,webpack 从这个文件开始,找到所有的依赖文件,将各个依赖文件模块通过 loader 和 plugins 处理后,然后打包在一起,最后输出一个浏览器可识别的 JS 文件。

Webpack 具有四个核心的概念,分别是 Entry(入口)Output(输出)loaderPlugins(插件)

Entry 是 webpack 的入口起点,它指示 webpack 应该从哪个模块开始着手,来作为其构建内部依赖图的开始。

Output 属性告诉 webpack 在哪里输出它所创建的打包文件,也可指定打包文件的名称,默认位置为 ./dist。

loader 可以理解为 webpack 的编译器,它使得 webpack 可以处理一些非 JavaScript 文件。在对 loader 进行配置的时候,test 属性,标志有哪些后缀的文件应该被处理,是一个正则表达式。use 属性,指定 test 类型的文件应该使用哪个 loader 进行预处理。常用的 loader 有 css-loader、style-loader 等。

插件可以用于执行范围更广的任务,包括打包、优化、压缩、搭建服务器等等,要使用一个插件,一般是先使用 npm 包管理器进行安装,然后在配置文件中引入,最后将其实例化后传递给 plugins 数组属性。

使用 webpack 的确能够提供我们对于项目的管理,但是它的缺点就是调试和配置起来太麻烦了。但现在 webpack4.0 的免配置一定程度上解决了这个问题。但是我感觉就是对我来说,就是一个黑盒,很多时候出现了问题,没有办法很好的定位。

js模块规范

  • 第一种是 CommonJS 方案,它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。

  • 第二种是AMD方案,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。

  • 第三种是CMD方案,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和 require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。参考60

  • 第四种方案是ES6提出的方案,使用 import 和 export 的形式来导入导出模块。这种方案和上面三种方案都不同。

commonJS 与 ES6差异

  • CommonJS 是运行时加载,ES6 模块是编译时输出接口
  • CommonJS 输出的是一个值的复制,ES6 输出的是值的引用
  • ES6 module 在编译期间会将所有 import 提升到顶部,commonjs 不会提升 require

AMD 和 CMD 的区别

  • 第一个方面是在模块定义时对依赖的处理不同。AMD 推崇依赖前置,在定义模块的时候就要声明其依赖的模块。而 CMD 推崇 就近依赖,只有在用到某个模块的时候再去 require。
  • 第二个方面是对依赖模块的执行时机处理不同。首先 AMD 和 CMD 对于模块的加载方式都是异步加载,不过它们的区别在于模块的执行时机,AMD 在依赖模块加载完成后就直接执行依赖模块,依赖模块的执行顺序和我们书写的顺序不一定一致。而 CMD 在依赖模块加载完成后并不执行,只是下载而已,等到所有的依赖模块都加载好后,进入回调函数逻辑,遇到 require 语句 的时候才执行对应的模块,这样模块的执行顺序就和我们书写的顺序保持一致了。

module.exports 与 exports

  • exports对象module 对象的一个属性,在初始时 module.exports 和 exports 指向同一块内存区域
var module = {
	exports:{}
}
var exports = module.exports
  • 模块导出的是 module.exports , exports 只是对它的引用,在不改变exports 内存的情况下,修改 exports 的值可以改变module.exports的值 导出时尽量使用 module.exports ,以免因为各种赋值导致的混乱

继承

  • 原型链继承:Child.prototype = new Parent(); 缺点是引用类型的实例被所有实例共享
  • 构造函数(经典继承):优点是可以从child向parent传参,缺点是方法在构造函数里定义,每次创建实例都得创建一遍方法
function Child(){
   Parent.call(this, name)
  • 组合继承:缺点是调用两次父构造函数:
  1. Child.prototype = new Parent();
  2. var child1 = new Child('kevin', '18');
function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
    console.log(this.name)
}
function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}
Child.prototype = new Parent();
var child1 = new Child('kevin', '18');
console.log(child1)
  • 原型式继承:缺点是包含引用类型的属性值始终都会共享相应的值
function createObj(o) {
    function F(){}
    F.prototype = o;
    return new F();
}
var person1 = createObj(person);
  • 寄生式继承:缺点是方法在构造函数里定义,每次创建实例都得创建一遍方法
function createObj (o) {
    var clone = Object.create(o);
    clone.sayName = function () {
        console.log('hi');
    }
    return clone;
}
  • 寄生组合式继承:间接让child.prototype访问parent.prototype,只调用一次parent构造函数
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();

具体是

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}
function prototype(child, parent) {
    var prototype = object(parent.prototype);
    prototype.constructor = child;
    child.prototype = prototype;
}
// 当我们使用的时候:
prototype(Child, Parent);

闭包

从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。

从实践角度:以下函数才算是闭包: 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回) 在代码中引用了自由变量

setTimeout 实现 SetInterval

function myInterval(fn, time) {
  let context = this;
  setTimeout(() => {
    fn.call(context);
    myInterval(fn, time);
  }, time);
}

函数柯里化

function curry(fn, args) {
    var length = fn.length;
    args = args || [];
    return function() {
        var _args = args.slice(0),
            arg, i;
        for (i = 0; i < arguments.length; i++) {
            arg = arguments[i];
            _args.push(arg);
        }
        if (_args.length < length) {
            return curry.call(this, fn, _args);
        }
        else {
            return fn.apply(this, _args);
        }
    }
}

var fn = curry(function(a, b, c) {
    console.log([a, b, c]);
});

深浅拷贝

浅拷贝

function shallowCopy(object) {
  // 只拷贝对象
  if (!object || typeof object !== "object") return;

  // 根据 object 的类型判断是新建一个数组还是对象
  let newObject = Array.isArray(object) ? [] : {};

  // 遍历 object,并且判断是 object 的属性才拷贝
  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObject[key] = object[key];
    }
  }
  return newObject;
}

深拷贝

function deepCopy(object) {
  if (!object || typeof object !== "object") return object;

  let newObject = Array.isArray(object) ? [] : {};

  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObject[key] = deepCopy(object[key]);
    }
  }
  return newObject;
}

字符串去掉首尾空格

function myTrim1(str){
    return str.replace(/^\s+|\s+$/g,'')
}

数组随机排序(洗牌算法)

function randomSort(arr) {
  var result = [];

  while (arr.length > 0) {
    var randomIndex = Math.floor(Math.random() * arr.length);
    result.push(arr[randomIndex]);
    arr.splice(randomIndex, 1);
  }
  return result;
}

12000000.11 转化为『12,000,000.11』

function format(number) {
  return number && number.replace(/(?!^)(?=(\d{3})+\.)/g, ",");
}

前端性能优化

第一个方面是页面的内容方面

(1)通过文件合并、css 雪碧图、使用 base64 等方式来减少 HTTP 请求数,避免过多的请求造成等待的情况。

(2)通过 DNS 缓存等机制来减少 DNS 的查询次数。

(3)通过设置缓存策略,对常用不变的资源进行缓存。

(4)使用延迟加载的方式,来减少页面首屏加载时需要请求的资源。延迟加载的资源当用户需要访问时,再去请求加载。

(5)通过用户行为,对某些资源使用预加载的方式,来提高用户需要访问资源时的响应速度。

第二个方面是服务器方面

(1)使用CDN服务,来提高用户对于资源请求时的响应速度。

(2)服务器端启用 Gzip、Deflate 等方式对于传输的资源进行压缩,减小文件的体积。

(3)尽可能减小 cookie 的大小,并且通过将静态资源分配到其他域名下,来避免对静态资源请求时携带不必要的 cookie

第三个方面是 CSS 和 JavaScript 方面

(1)把样式表放在页面的head标签中,减少页面的首次渲染的时间。

(2)避免使用 @import 标签。

(3)尽量把 js 脚本放在页面底部或者使用 defer 或 async 属性,避免脚本的加载和执行阻塞页面的渲染。

(4)通过对 JavaScript 和 CSS 的文件进行压缩,来减小文件的体积。

网络攻击

XSS攻击

XSS 攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。

XSS 的本质是因为网站没有对恶意代码进行过滤,与正常的代码混合在一起了,浏览器没有办法分辨哪些脚本是可信的,从而导致了恶意代码的执行。

XSS 一般分为存储型、反射型和 DOM 型。

存储型指的是恶意代码提交到了网站的数据库中,当用户请求数据的时候,服务器将其拼接为 HTML 后返回给了用户,从而导致了恶意代码的执行。

反射型指的是XSS脚本来自当前http请求中,攻击者构建了特殊的 URL,当服务器接收到请求后,从 URL 中获取数据,拼接到 HTML 后返回,从而导致了恶意代码的执行。

DOM 型指的是攻击者构建了特殊的 URL,用户打开网站后,js 脚本从 URL 中获取数据,从而导致了恶意代码的执行。

XSS 攻击的预防可以从两个方面入手,一个是恶意代码提交的时候,一个是浏览器执行恶意代码的时候。

对于第一个方面,如果我们对存入数据库的数据都进行的转义处理,但是一个数据可能在多个地方使用,有的地方可能不需要转义,由于我们没有办法判断数据最后的使用场景,所以直接在输入端进行恶意代码的处理,其实是不太可靠的。

因此我们可以从浏览器的执行来进行预防,一种是使用纯前端的方式,不用服务器端拼接后返回。另一种是对需要插入到 HTML 中的代码做好充分的转义。转义是指对于 DOM 型的攻击,主要是前端脚本的不可靠而造成的,我们对于数据获取渲染和字符串拼接的时候应该对可能出现的恶意代码情况进行判断。

还有一些方式,比如使用 CSP ,CSP 的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码的注入攻击。

还可以对一些敏感信息进行保护,比如 cookie 使用 http-only ,使得脚本无法获取。也可以使用验证码,避免脚本伪装成用户执行一些操作。

React 防止 XSS

  1. React DOM 在渲染所有输入内容之前,默认会进行转义
  2. Babel 会把 JSX 编译成 React.createElement() 的函数调用,最终返回一个 ReactElement。有个属性是 $$typeof,它是用来标记此对象是一个 ReactElement,React 在进行渲染前会通过此属性进行校验,校验不通过将会抛出上面的错误。React 利用这个属性来防止通过构造特殊的 Children 来进行的 XSS 攻击,原因是 $$typeof 是个 Symbol 类型,进行 JSON 转换后会 Symbol 值会丢失,无法在前后端进行传输。

CSRF攻击

CSRF 攻击指的是跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。

CSRF 攻击的本质是利用了 cookie 会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。

一般的 CSRF 攻击类型有三种:

第一种是GET类型的 CSRF 攻击,比如在网站中的一个 img 标签里构建一个请求,当用户打开这个网站的时候就会自动发起提 交。

第二种是POST类型的 CSRF 攻击,比如说构建一个表单,然后隐藏它,当用户进入页面时,自动提交这个表单。

第三种是链接类型的 CSRF 攻击,比如说在 a 标签的 href 属性里构建一个请求,然后诱导用户去点击。

CSRF 可以用下面几种方法来防护:

第一种是同源检测的方法,服务器根据 http 请求头中 origin 或者 referer 信息来判断请求是否为允许访问的站点,从而对请求进行过滤。当 origin 或者 referer 信息都不存在的时候,直接阻止。这种方式的缺点是有些情况下 referer 可以被伪造。还有就是我们这种方法同时把搜索引擎的链接也给屏蔽了,所以一般网站会允许搜索引擎的页面请求,但是相应的页面请求这种请求方式也可能被攻击者给利用。

第二种方法是使用CSRF Token来进行验证,服务器向用户返回一个随机数 Token ,当网站再次发起请求时,在请求参数中加入服务器端返回的 token ,然后服务器对这个 token 进行验证。这种方法解决了使用 cookie 单一验证方式时,可能会被冒用的问题,但是这种方法存在一个缺点就是,我们需要给网站中的所有请求都添加上这个 token,操作比较繁琐。还有一个问题是一般不会只有一台网站服务器,如果我们的请求经过负载平衡转移到了其他的服务器,但是这个服务器的 session 中没有保留这个 token 的话,就没有办法验证了。这种情况我们可以通过改变 token 的构建方式来解决。

第三种方式使用双重 Cookie 验证的办法,服务器在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串,然后当用户再次向服务器发送请求的时候,从 cookie 中取出这个字符串,添加到 URL 参数中,然后服务器通过对 cookie 中的数据和参数中的数据进行比较,来进行验证。使用这种方式是利用了攻击者只能利用 cookie,但是不能访问获取 cookie 的特点。并且这种方法比 CSRF Token 的方法更加方便,并且不涉及到分布式访问的问题。这种方法的缺点是如果网站存在 XSS 漏洞的,那么这种方式会失效。同时这种方式不能做到子域名的隔离。

第四种方式是使用在设置 cookie 属性的时候设置 Samesite ,限制 cookie 不能作为被第三方使用,从而可以避免被攻击者利用。Samesite 一共有两种模式,一种是严格模式,在严格模式下 cookie 在任何情况下都不可能作为第三方 Cookie 使用,在宽松模式下,cookie 可以被请求是 GET 请求,且会发生页面跳转的请求所使用。

垃圾回收

引用计数:该算法的原理比较简单,就是看对象是否还有其他引用指向它,如果没有指向该对象的引用,则该对象会被视为垃圾并被垃圾回收器回收

问题:循环引用,内存泄漏

Mark-Sweep(标记清除):分为标记清除两个阶段,在标记阶段会遍历堆中的所有对象,然后标记活着的对象,在清除阶段中,会将死亡的对象进行清除。Mark-Sweep算法主要是通过判断某个对象是否可以被访问到,从而知道该对象是否应该被回收,具体步骤如下:

  • 垃圾回收器会在内部构建一个根列表,用于从根节点出发去寻找那些可以被访问到的变量。比如在JavaScript中,window全局对象可以看成一个根节点。
  • 然后,垃圾回收器从所有根节点出发,遍历其可以访问到的子节点,并将其标记为活动的,根节点不能到达的地方即为非活动的,将会被视为垃圾。
  • 最后,垃圾回收器将会释放所有非活动的内存块,并将其归还给操作系统。

问题:内存碎片

标记整理:该算法主要就是用来解决内存的碎片化问题的,回收过程中将死亡对象清除后,在整理的过程中,会将活动的对象往堆内存的一端进行移动,移动完成后再清理掉边界外的全部内存

避免内存泄漏

  • 少创建全局变量
  • 少闭包
  • 手动清除定时器
  • 清除DOM引用

CSS

BFC

  • 同一个 BFC 下外边距会发生折叠,可以改成两个bfc就可以取消折叠
  • BFC 可以包含浮动的元素(清除浮动),加个overflow:hidden
  • BFC 可以阻止元素被浮动元素覆盖

float与absolute区别

  • float 文本留出位置
  • absolute 文本无视盒子

文本单行多行溢出

.text {
  overflow: hidden; 
  text-overflow: ellipsis; 
  white-space: nowrap;
}
.text {
  display: -webkit-box;
  overflow: hidden;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

居中

flex布局 一共八个一行四个

.outer {
  display: flex;
  height: 100px;
  flex-wrap: wrap;
  justify-content: flex-start;
  
}

.inner {
  box-sizing: border-box;
  flex-direction: row;
  flex: 0 1 25%;  
  background-color: red;
  border: 1px solid black;
}
  • flex: 1 === flex: 1 1 0px 平分
  • flex: 1 1 auto 根据内容比例分配

层叠上下文

截屏2021-04-22 下午12.14.22.png

图形

.outer {
  width: 100px;
  height: 100px;
  border-radius: 50%; 
  background-color: red;
}

半圆

.outer {
  width: 100px;
  height: 50px;
  border-radius: 100px 100px 0 0; 
  background-color: red;
}

扇形

border-radius: 100px 0 0; //第一个值为左上,第二个值为右上和左下

三角形

.outer {
  width: 0;
  height: 0;
  border-botttom: 100px solid pink; 
  border-left: 50px solid transparent;
  border-right: 50px solid transparent;  
}

计算机网络

image.png

image.png

webpack

一、webpack的打包原理

  1. 识别入口文件
  2. 通过逐层识别模块依赖(Commonjs、amd或者es6的import,webpack都会对其进行分析,来获取代码的依赖)
  3. webpack做的就是分析代码,转换代码,编译代码,输出代码
  4. 最终形成打包后的代码 二、什么是loader

loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中

处理一个文件可以使用多个loader,loader的执行顺序和配置中的顺序是相反的,即最后一个loader最先执行,第一个loader最后执行

第一个执行的loader接收源文件内容作为参数,其它loader接收前一个执行的loader的返回值作为参数,最后执行的loader会返回此模块的JavaScript源码

三、什么是plugin

在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果。

四、loader和plugin的区别

对于loader,它是一个转换器,将A文件进行编译形成B文件,这里操作的是文件,比如将A.scss转换为A.css,单纯的文件转换过程

plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务

readyState--ajax

readyState是XMLHttpRequest对象的一个属性,用来标识当前XMLHttpRequest对象处于什么状态。 readyState总共有5个状态值,分别为0~4,每个值代表了不同的含义

  • 0:初始化,XMLHttpRequest对象还没有完成初始化
  • 1:载入,XMLHttpRequest对象开始发送请求
  • 2:载入完成,XMLHttpRequest对象的请求发送完成
  • 3:解析,XMLHttpRequest对象开始读取服务器的响应
  • 4:完成,XMLHttpRequest对象读取服务器响应结束

项目

oauth2.0风险

风险1: redirect_url 回调域名欺骗

服务端必须验证clientid注册的应用与redirect_url是对应的,否则redirect_url会被伪造成第三方欺诈域名,直接导致服务器返回Code被泄露;

服务器生成的临时Code必须是一次有效,客户端使用一次后立即失效并且有效期很短,一般推荐30s有效期,由于Code是通过redirect_url浏览器回调传输容易被截取,可以保证临时Code被客户端正常消费后不会被再次使用;

风险2:redirect_url XSS跨域攻击

比如构造一个认证请求,redirect_uri = app.com/test?callba… 服务器端需要对redirect_url进行检查禁止特殊字符输入,并且对redirect_url进行全匹配,不做模糊匹配可以杜绝XSS攻击;

风险3:State 防止CSRF

认证请求url中state参数是最容易被忽略的,大部分IDP不会强制要求客户端使用state参数;

客户端每次请求生成唯一字符串在请求中放到state参数中,服务端认证成功返回Code会带上state参数,客户端验证state是否是自己生成的唯一串,可以确定这次请求是有客户端真实发出的,不是黑客伪造的请求;

风险4:Access_Token泄露

由于Access_Token是通过http协议从服务器端传输给客户端,为了防止旁路监听泄露Access_Token,服务器必须提供https来保证传输通道的安全性(TSL的要求);

  • 客户端获取Access_Token,应该在后台与服务端交互获取Access_Token,不允许Access_Token传给前端直接使用;

需要保证Access_Token信息的不可猜测行,以防止被猜测得到;

风险5:令牌有效性漏洞

维持refresh_token和第三方应用的绑定,刷新失效机制的设计不允许长期有效的token存在;

react-infinite-scroller

  1. offsetParent offsetParent返回一个指向最近的包含该元素的定位元素。offsetParent很有用,因为计算offsetTop和offsetLeft都是相对于offsetParent边界的。

ele.offsetParent为null的3种情况:

  • ele 为body
  • ele 的position为fixed
  • ele 的display为none 此组件中offsetParent处理了2种情况:
  • 在useWindow的情况下(即事件绑定在window,滚动作用在body),通过递归获取offsetParent到达顶端的高度(offsetTop)。
calculateTopPosition(el) {
 if (!el) {
   return 0;   
 }
 return el.offsetTop + this.calculateTopPosition(el.offsetParent);   
}

通过判断offsetParent不为null的情况,确保滚动组件正常显示

if (
    offset < Number(this.props.threshold) &&
    (el && el.offsetParent !== null)
  ) {/* ... */ }
  1. scrollHeight 和 clientHeight 在无滚动的情况下,scrollHeight 和 clientHeight 相等,都为height+padding*2

有滚动的情况下,scrollHeight 表示实际内容高度,clientHeight 表示视口高度。

  1. 每次执行loadMore前卸载事件。 确保不会重复(过多)执行loadMore,因为先卸载事件再执行loadMore,可以确保在执行过程中,scroll事件是无效的,然后再每次componentDidUpdate的时候重新绑定事件。

react 虚拟DOM

在原生的JavaScript程序中,我们直接对DOM进行创建和更改,而DOM元素通过我们监听的事件和我们的应用程序进行通讯。

React会先将你的代码转换成一个JavaScript对象,然后这个JavaScript对象再转换成真实DOM。这个JavaScript对象就是所谓的虚拟DOM。当我们需要创建或更新元素时,React首先会让这个VitrualDom对象进行创建和更改,然后再将VitrualDom对象渲染成真实DOM;当我们需要对DOM进行事件监听时,首先对VitrualDom进行事件监听,VitrualDom会代理原生的DOM事件从而做出响应。

优点:提升开发效率

  • 你只需要告诉React你想让视图处于什么状态,React则通过VitrualDom确保DOM与该状态相匹配。你不必自己去完成属性操作事件处理DOM更新,React会替你完成这一切。
  • 重复渲染时借助diff更高效的计算更新

过程

  1. babeljsx转化为React.createElement(...), 因为提前编译,所以不能动态选择类型
  2. createElement函数对keyref等特殊的props进行处理,并获取defaultProps对默认props进行赋值,并且对传入的孩子节点进行处理,最终构造成一个ReactElement对象(所谓的虚拟DOM)。
  3. ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM

react diff

tree diff

  • Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。 同层节点比较。React 通过updateDepth对 Virtual DOM 树进行层级控制,只会对相同颜色方框内的 DOM 节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。

component diff

  • 如果是同一类型的组件,按照原策略继续比较 virtual DOM tree。
  • 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
  • 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。
  • 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。

element diff

  • 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。
  • 提供了三种节点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)

设计模式

  1. 单例模式:保证一个类只有一个实例,并提供一个访问它的全局访问点 ;
    应用场景:一个无状态的类使用单例模式节省内存资源。
  2. 抽象工厂:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们的具体类。
    应用场景:一系列相互依赖的对象有不同的具体实现。提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合。
  3. 工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类的实例化延迟到了子类。
    应用场景:由于需求的变化,一个类的子类经常面临着剧烈的变化,但他却拥有比较稳定的接口。使用一种封装机制来“隔离这种易变对象的变化”,工厂方法定义一个用于创建对象的接口,让子类来确定创建哪一个具体类的对象,将对象的实例化延迟。
  4. 建造模式:将一个复杂对象的构建与他的表示相分离,使得同样的构建过程可以创建不同的表示。
    应用场景:一个类的各个组成部分的具体实现类或者算法经常面临着变化,但是将他们组合在一起的算法却相对稳定。提供一种封装机制 将稳定的组合算法于易变的各个组成部分隔离开来。
  5. 原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新的对象。
    应用场景:用new创建一个对象需要非常繁琐的数据准备或者权限
  6. 迭代器模式:提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露该对象的内部表示。
    应用场景:迭代。
  7. 观察者模式:定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知自动更新。
    应用场景: 某个实例的变化将影响其他多个对象。
  8. 模板方法:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,TemplateMethod使得子类可以不改变一个算法的结构即可以重定义该算法的某些特定步骤。
    应用场景:一个操作的步骤稳定,而具体细节的改变延迟的子类
  9. 命令模式:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队和记录请求日志,以及支持可撤销的操作。
    应用场景:将命令者与执行者完全解耦。
  10. 状态模式:允许对象在其内部状态改变时改变他的行为。对象看起来似乎改变了他的类。
    应用场景:一个对象的内部状态改变时,他的行为剧烈的变化。
  11. 中介者模式:用一个中介对象封装一些列的对象交互。
  12. 访问者模式:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这个元素的新操作。

操作系统

进程与线程

进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。每个进程的内存是相互独立的。

线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。

进程:(线程+内存+文件/网络句柄)

线程:(栈+PC+TLS)

  • PC:程序计数器
  • TLS:线程的独立内存
  1. 进程要分配一大部分的内存,而线程只需要分配一部分栈就可以了.
  2. 一个程序至少有一个进程,一个进程至少有一个线程.
  3. 进程是资源分配的最小单位,线程是程序执行的最小单位。
  4. 一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行.