关于前端:百度前端高频面试题附答案

TCP粘包是怎么回事,如何解决?

默认状况下, TCP 连贯会启⽤提早传送算法 (Nagle 算法), 在数据发送之前缓存他们. 如果短时间有多个数据发送, 会缓冲到⼀起作⼀次发送 (缓冲⼤⼩⻅ socket.bufferSize ), 这样能够缩小 IO 耗费提⾼性能.

如果是传输⽂件的话, 那么基本不⽤解决粘包的问题, 来⼀个包拼⼀个包就好了。然而如果是多条音讯, 或者是别的⽤途的数据那么就须要解决粘包.

上面看⼀个例⼦, 间断调⽤两次 send 别离发送两段数据 data1 和 data2, 在接收端有以下⼏种常⻅的状况:
A. 先接管到 data1, 而后接管到 data2 .
B. 先接管到 data1 的局部数据, 而后接管到 data1 余下的局部以及 data2 的全副.
C. 先接管到了 data1 的全副数据和 data2 的局部数据, 而后接管到了 data2 的余下的数据.
D. ⼀次性接管到了 data1 和 data2 的全副数据.

其中的 BCD 就是咱们常⻅的粘包的状况. ⽽对于解决粘包的问题, 常⻅的解决⽅案有:

  • 屡次发送之前距离⼀个等待时间:只须要等上⼀段时间再进⾏下⼀次 send 就好, 适⽤于交互频率特地低的场景. 毛病也很显著, 对于⽐较频繁的场景⽽⾔传输效率切实太低,不过⼏乎不⽤做什么解决.
  • 敞开 Nagle 算法:敞开 Nagle 算法, 在 Node.js 中你能够通过 socket.setNoDelay() ⽅法来敞开 Nagle 算法, 让每⼀次 send 都不缓冲间接发送。该⽅法⽐较适⽤于每次发送的数据都⽐较⼤ (但不是⽂件那么⼤), 并且频率不是特地⾼的场景。如果是每次发送的数据量⽐较⼩, 并且频率特地⾼的, 敞开 Nagle 纯属⾃废文治。另外, 该⽅法不适⽤于⽹络较差的状况, 因为 Nagle 算法是在服务端进⾏的包合并状况, 然而如果短时间内客户端的⽹络状况不好, 或者应⽤层因为某些起因不能及时将 TCP 的数据 recv, 就会造成多个包在客户端缓冲从⽽粘包的状况。 (如果是在稳固的机房外部通信那么这个概率是⽐较⼩能够抉择疏忽的)
  • 进⾏封包/拆包: 封包/拆包是⽬前业内常⻅的解决⽅案了。即给每个数据包在发送之前, 于其前/后放⼀些有特色的数据, 而后收到数据的时 候依据特色数据宰割进去各个数据包。

—-问题知识点分割线—-

元素的层叠程序

层叠程序,英文称作 stacking order,示意元素产生层叠时有着特定的垂直显示程序。上面是盒模型的层叠规定:

(1)背景和边框:建设以后层叠上下文元素的背景和边框。
(2)负的z-index:以后层叠上下文中,z-index属性值为负的元素。
(3)块级盒:文档流内非行内级非定位后辈元素。
(4)浮动盒:非定位浮动元素。
(5)行内盒:文档流外行内级非定位后辈元素。
(6)z-index:0:层叠级数为0的定位元素。
(7)正z-index:z-index属性值为正的定位元素。

留神: 当定位元素z-index:auto,生成盒在以后层叠上下文中的层级为 0,不会建设新的层叠上下文,除非是根元素。

—-问题知识点分割线—-

call apply bind

题目形容:手写 call apply bind 实现

实现代码如下:

Function.prototype.myCall = function (context, ...args) {
  if (!context || context === null) {
    context = window;
  }
  // 发明惟一的key值  作为咱们结构的context外部办法名
  let fn = Symbol();
  context[fn] = this; //this指向调用call的函数
  // 执行函数并返回后果 相当于把本身作为传入的context的办法进行调用了
  return context[fn](...args);
};

// apply原理统一  只是第二个参数是传入的数组
Function.prototype.myApply = function (context, args) {
  if (!context || context === null) {
    context = window;
  }
  // 发明惟一的key值  作为咱们结构的context外部办法名
  let fn = Symbol();
  context[fn] = this;
  // 执行函数并返回后果
  return context[fn](...args);
};

//bind实现要简单一点  因为他思考的状况比拟多 还要波及到参数合并(相似函数柯里化)

Function.prototype.myBind = function (context, ...args) {
  if (!context || context === null) {
    context = window;
  }
  // 发明惟一的key值  作为咱们结构的context外部办法名
  let fn = Symbol();
  context[fn] = this;
  let _this = this;
  //  bind状况要简单一点
  const result = function (...innerArgs) {
    // 第一种状况 :若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符应用,则不绑定传入的 this,而是将 this 指向实例化进去的对象
    // 此时因为new操作符作用  this指向result实例对象  而result又继承自传入的_this 依据原型链常识可得出以下论断
    // this.__proto__ === result.prototype   //this instanceof result =>true
    // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
    if (this instanceof _this === true) {
      // 此时this指向指向result的实例  这时候不须要扭转this指向
      this[fn] = _this;
      this[fn](...[...args, ...innerArgs]); //这里应用es6的办法让bind反对参数合并
    } else {
      // 如果只是作为一般函数调用  那就很简略了 间接扭转this指向为传入的context
      context[fn](...[...args, ...innerArgs]);
    }
  };
  // 如果绑定的是构造函数 那么须要继承构造函数原型属性和办法
  // 实现继承的形式: 应用Object.create
  result.prototype = Object.create(this.prototype);
  return result;
};

//用法如下

// function Person(name, age) {
//   console.log(name); //'我是参数传进来的name'
//   console.log(age); //'我是参数传进来的age'
//   console.log(this); //构造函数this指向实例对象
// }
// // 构造函数原型的办法
// Person.prototype.say = function() {
//   console.log(123);
// }
// let obj = {
//   objName: '我是obj传进来的name',
//   objAge: '我是obj传进来的age'
// }
// // 一般函数
// function normalFun(name, age) {
//   console.log(name);   //'我是参数传进来的name'
//   console.log(age);   //'我是参数传进来的age'
//   console.log(this); //一般函数this指向绑定bind的第一个参数 也就是例子中的obj
//   console.log(this.objName); //'我是obj传进来的name'
//   console.log(this.objAge); //'我是obj传进来的age'
// }

// 先测试作为结构函数调用
// let bindFun = Person.myBind(obj, '我是参数传进来的name')
// let a = new bindFun('我是参数传进来的age')
// a.say() //123

// 再测试作为一般函数调用
// let bindFun = normalFun.myBind(obj, '我是参数传进来的name')
//  bindFun('我是参数传进来的age')

—-问题知识点分割线—-

常见的HTTP申请办法

  • GET: 向服务器获取数据;
  • POST:将实体提交到指定的资源,通常会造成服务器资源的批改;
  • PUT:上传文件,更新数据;
  • DELETE:删除服务器上的对象;
  • HEAD:获取报文首部,与GET相比,不返回报文主体局部;
  • OPTIONS:询问反对的申请办法,用来跨域申请;
  • CONNECT:要求在与代理服务器通信时建设隧道,应用隧道进行TCP通信;
  • TRACE: 回显服务器收到的申请,次要⽤于测试或诊断。

—-问题知识点分割线—-

字符串模板

function render(template, data) {
    const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
    if (reg.test(template)) { // 判断模板里是否有模板字符串
        const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段
        template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
        return render(template, data); // 递归的渲染并返回渲染后的构造
    }
    return template; // 如果模板没有模板字符串间接返回
}

测试:

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let person = {
    name: '布兰',
    age: 12
}
render(template, person); // 我是布兰,年龄12,性别undefined

—-问题知识点分割线—-

Chrome 关上一个页面须要启动多少过程?别离有哪些过程?

关上 1 个页面至多须要 1 个网络过程、1 个浏览器过程、1 个 GPU 过程以及 1 个渲染过程,共 4 个;最新的 Chrome 浏览器包含:1 个浏览器(Browser)主过程、1 个 GPU 过程、1 个网络(NetWork)过程、多个渲染过程和多个插件过程。

  • 浏览器过程:次要负责界面显示、用户交互、子过程治理,同时提供存储等性能。
  • 渲染过程:外围工作是将 HTML、CSS 和 JavaScript 转换为用户能够与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该过程中,默认状况下,Chrome 会为每个 Tab 标签创立一个渲染过程。出于平安思考,渲染过程都是运行在沙箱模式下。
  • GPU 过程:其实,Chrome 刚开始公布的时候是没有 GPU 过程的。而 GPU 的应用初衷是为了实现 3D CSS 的成果,只是随后网页、Chrome 的 UI 界面都抉择采纳 GPU 来绘制,这使得 GPU 成为浏览器广泛的需要。最初,Chrome 在其多过程架构上也引入了 GPU 过程。
  • 网络过程:次要负责页面的网络资源加载,之前是作为一个模块运行在浏览器过程外面的,直至最近才独立进去,成为一个独自的过程。
  • 插件过程:次要是负责插件的运行,因插件易解体,所以须要通过插件过程来隔离,以保障插件过程解体不会对浏览器和页面造成影响。

—-问题知识点分割线—-

懒加载的特点

  • 缩小无用资源的加载:应用懒加载显著缩小了服务器的压力和流量,同时也减小了浏览器的累赘。
  • 晋升用户体验: 如果同时加载较多图片,可能须要期待的工夫较长,这样影响了用户体验,而应用懒加载就能大大的进步用户体验。
  • 避免加载过多图片而影响其余资源文件的加载 :会影响网站利用的失常应用。

—-问题知识点分割线—-

Nginx的概念及其工作原理

Nginx 是一款轻量级的 Web 服务器,也能够用于反向代理、负载平衡和 HTTP 缓存等。Nginx 应用异步事件驱动的办法来解决申请,是一款面向性能设计的 HTTP 服务器。

传统的 Web 服务器如 Apache 是 process-based 模型的,而 Nginx 是基于event-driven模型的。正是这个次要的区别带给了 Nginx 在性能上的劣势。

Nginx 架构的最顶层是一个 master process,这个 master process 用于产生其余的 worker process,这一点和Apache 十分像,然而 Nginx 的 worker process 能够同时解决大量的HTTP申请,而每个 Apache process 只能解决一个。

—-问题知识点分割线—-

::before 和 :after 的双冒号和单冒号有什么区别?

(1)冒号(:)用于CSS3伪类,双冒号(::)用于CSS3伪元素。
(2)::before就是以一个子元素的存在,定义在元素主体内容之前的一个伪元素。并不存在于dom之中,只存在在页面之中。

留神: :before:after 这两个伪元素,是在CSS2.1里新呈现的。起初,伪元素的前缀应用的是单冒号语法,但随着Web的进化,在CSS3的标准里,伪元素的语法被批改成应用双冒号,成为::before::after

—-问题知识点分割线—-

代码输入后果

 var a = 10; 
 var obt = { 
   a: 20, 
   fn: function(){ 
     var a = 30; 
     console.log(this.a)
   } 
 }
 obt.fn();  // 20
 obt.fn.call(); // 10
 (obt.fn)(); // 20

输入后果: 20 10 20

解析:

  1. obt.fn(),fn是由obt调用的,所以其this指向obt对象,会打印出20;
  2. obt.fn.call(),这里call的参数啥都没写,就示意null,咱们晓得如果call的参数为undefined或null,那么this就会指向全局对象this,所以会打印出 10;
  3. (obt.fn)(), 这里给表达式加了括号,而括号的作用是扭转表达式的运算程序,而在这里加与不加括号并无影响;相当于 obt.fn(),所以会打印出 20;

—-问题知识点分割线—-

什么是原型什么是原型链?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

</body>
<script>
    function Person () {    }    var person  = new Person();    person.name = 'Kevin';    console.log(person.name) // Kevin

    // prototype
    function Person () {    }    Person.prototype.name = 'Kevin';    var person1 = new Person();    var person2 = new Person();    console.log(person1.name)// Kevin
    console.log(person2.name)// Kevin

    // __proto__
    function Person () {    }    var person = new Person();    console.log(person.__proto__ === Person.prototype) // true

    //constructor
    function Person() {    }    console.log(Person === Person.prototype.constructor) // true

    //综上所述
    function Person () {    }    var person = new Person()    console.log(person.__proto__ == Person.prototype) // true
    console.log(Person.prototype.constructor == Person) // true
    //顺便学习一下ES5得办法,能够取得对象得原型
    console.log(Object.getPrototypeOf(person) === Person.prototype) // true

    //实例与原型
    function Person () {    }    Person.prototype.name = 'Kevin';    var person = new Person();    person.name = 'Daisy';    console.log(person.name) // Daisy
    delete person.name;    console.log(person.name) // Kevin

    //原型得原型
    var obj = new Object();    obj.name = 'Kevin',    console.log(obj.name) //Kevin

     //原型链
     console.log(Object.prototype.__proto__ === null) //true
     // null 示意"没用对象" 即该处不应该有值

     // 补充
     function Person() {     }     var person = new Person()     console.log(person.constructor === Person) // true
     //当获取person.constructor时,其实person中并没有constructor属性,当不能读取到constructor属性时,会从person的原型
     //也就是Person.prototype中读取时,正好原型中有该属性,所以
     person.constructor === Person.prototype.constructor

     //__proto__
     //其次是__proto__,绝大部分浏览器都反对这个非标准的办法拜访原型,然而它并不存在于Person.prototype中,实际上,它
     // 是来自与Object.prototype,与其说是一个属性,不如说是一个getter/setter,当应用obj.__proto__时,能够了解成返回了
     // Object.getPrototypeOf(obj)
     总结:          1、当一个对象查找属性和办法时会从本身查找,如果查找不到则会通过__proto__指向被实例化的构造函数的prototype     2、隐式原型也是一个对象,是指向咱们构造函数的原型     3、除了最顶层的Object对象没有__proto_,其余所有的对象都有__proto__,这是隐式原型     4、隐式原型__proto__的作用是让对象通过它来始终往上查找属性或办法,直到找到最顶层的Object的__proto__属性,它的值是null,这个查找的过程就是原型链



</script>
</html>

—-问题知识点分割线—-

并发与并行的区别?

  • 并发是宏观概念,我别离有工作 A 和工作 B,在一段时间内通过工作间的切换实现了这两个工作,这种状况就能够称之为并发。
  • 并行是宏观概念,假如 CPU 中存在两个外围,那么我就能够同时实现工作 A、B。同时实现多个工作的状况就能够称之为并行。

—-问题知识点分割线—-

Unicode、UTF-8、UTF-16、UTF-32的区别?

(1)Unicode

在说Unicode之前须要先理解一下ASCII码:ASCII 码(American Standard Code for Information Interchange)称为美国规范信息替换码。

  • 它是基于拉丁字母的一套电脑编码零碎。
  • 它定义了一个用于代表常见字符的字典。
  • 它蕴含了”A-Z”(蕴含大小写),数据”0-9″ 以及一些常见的符号。
  • 它是专门为英语而设计的,有128个编码,对其余语言无能为力

ASCII码能够示意的编码无限,要想示意其余语言的编码,还是要应用Unicode来示意,能够说UnicodeASCII 的超集。

Unicode全称 Unicode Translation Format,又叫做对立码、万国码、繁多码。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了对立并且惟一的二进制编码,以满足跨语言、跨平台进行文本转换、解决的要求。

Unicode的实现形式(也就是编码方式)有很多种,常见的是UTF-8UTF-16UTF-32USC-2

(2)UTF-8

UTF-8是应用最宽泛的Unicode编码方式,它是一种可变长的编码方式,能够是1—4个字节不等,它能够齐全兼容ASCII码的128个字符。

留神: UTF-8 是一种编码方式,Unicode是一个字符汇合。

UTF-8的编码规定:

  • 对于单字节的符号,字节的第一位为0,前面的7位为这个字符的Unicode编码,因而对于英文字母,它的Unicode编码和ACSII编码一样。
  • 对于n字节的符号,第一个字节的前n位都是1,第n+1位设为0,前面字节的前两位一律设为10,剩下的没有提及的二进制位,全副为这个符号的Unicode码 。

来看一下具体的Unicode编号范畴与对应的UTF-8二进制格局 :

编码范畴(编号对应的十进制数) 二进制格局
0x00—0x7F (0-127) 0xxxxxxx
0x80—0x7FF (128-2047) 110xxxxx 10xxxxxx
0x800—0xFFFF (2048-65535) 1110xxxx 10xxxxxx 10xxxxxx
0x10000—0x10FFFF (65536以上) 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

那该如何通过具体的Unicode编码,进行具体的UTF-8编码呢?步骤如下:

  • 找到该Unicode编码的所在的编号范畴,进而找到与之对应的二进制格局
  • Unicode编码转换为二进制数(去掉最高位的0)
  • 将二进制数从右往左一次填入二进制格局的X中,如果有X未填,就设为0

来看一个理论的例子:
” 字的Unicode编码是:0x9A6C,整数编号是39532 (1)首选确定了该字符在第三个范畴内,它的格局是 1110xxxx 10xxxxxx 10xxxxxx (2)39532对应的二进制数为1001 1010 0110 1100 (3)将二进制数填入X中,后果是:11101001 10101001 10101100

(3)UTF-16

1. 立体的概念

在理解UTF-16之前,先看一下立体的概念: Unicode编码中有很多很多的字符,它并不是一次性定义的,而是分区进行定义的,每个区寄存65536(216)个字符,这称为一个立体,目前总共有17 个立体。

最后面的一个立体称为根本立体,它的码点从0 — 216-1,写成16进制就是U+0000 — U+FFFF,那剩下的16个立体就是辅助立体,码点范畴是 U+10000—U+10FFFF

2. UTF-16 概念:

UTF-16也是Unicode编码集的一种编码模式,把Unicode字符集的形象码位映射为16位长的整数(即码元)的序列,用于数据存储或传递。Unicode字符的码位须要1个或者2个16位长的码元来示意,因而UTF-16也是用变长字节示意的。

3. UTF-16 编码规定:

  • 编号在 U+0000—U+FFFF 的字符(罕用字符集),间接用两个字节示意。
  • 编号在 U+10000—U+10FFFF 之间的字符,须要用四个字节示意。

4. 编码辨认

那么问题来了,当遇到两个字节时,怎么晓得是把它当做一个字符还是和前面的两个字节一起当做一个字符呢?

UTF-16 编码必定也思考到了这个问题,在根本立体内,从 U+D800 — U+DFFF 是一个空段,也就是说这个区间的码点不对应任何的字符,因而这些空段就能够用来映射辅助立体的字符。

辅助立体共有 220 个字符位,因而示意这些字符至多须要 20 个二进制位。UTF-16 将这 20 个二进制位分成两半,前 10 位映射在 U+D800 — U+DBFF,称为高位(H),后 10 位映射在 U+DC00 — U+DFFF,称为低位(L)。这就相当于,将一个辅助立体的字符拆成了两个根本立体的字符来示意。

因而,当遇到两个字节时,发现它的码点在 U+D800 —U+DBFF之间,就能够晓得,它前面的两个字节的码点应该在 U+DC00 — U+DFFF 之间,这四个字节必须放在一起进行解读。

5. 举例说明

以 “𡠀” 字为例,它的 Unicode 码点为 0x21800,该码点超出了根本立体的范畴,因而须要用四个字节来示意,步骤如下:

  • 首先计算超出局部的后果:0x21800 - 0x10000
  • 将下面的计算结果转为20位的二进制数,有余20位就在后面补0,后果为:0001000110 0000000000
  • 将失去的两个10位二进制数别离对应到两个区间中
  • U+D800 对应的二进制数为 1101100000000000, 将0001000110填充在它的后10 个二进制位,失去 1101100001000110,转成 16 进制数为 0xD846。同理,低位为 0xDC00,所以这个字的UTF-16 编码为 0xD846 0xDC00

(4) UTF-32

UTF-32 就是字符所对应编号的整数二进制模式,每个字符占四个字节,这个是间接进行转换的。该编码方式占用的贮存空间较多,所以应用较少。

比方“” 字的Unicode编号是:U+9A6C,整数编号是39532,间接转化为二进制:1001 1010 0110 1100,这就是它的UTF-32编码。

(5)总结

Unicode、UTF-8、UTF-16、UTF-32有什么区别?

  • Unicode 是编码字符集(字符集),而UTF-8UTF-16UTF-32是字符集编码(编码规定);
  • UTF-16 应用变长码元序列的编码方式,相较于定长码元序列的UTF-32算法更简单,甚至比同样是变长码元序列的UTF-8也更为简单,因为其引入了独特的代理对这样的代理机制;
  • UTF-8须要判断每个字节中的结尾标记信息,所以如果某个字节在传送过程中出错了,就会导致前面的字节也会解析出错;而UTF-16不会判断结尾标记,即便错也只会错一个字符,所以容错能力教强;
  • 如果字符内容全副英文或英文与其余文字混合,但英文占绝大部分,那么用UTF-8就比UTF-16节俭了很多空间;而如果字符内容全副是中文这样相似的字符或者混合字符中中文占绝大多数,那么UTF-16就占优势了,能够节俭很多空间;

—-问题知识点分割线—-

CSS预处理器/后处理器是什么?为什么要应用它们?

预处理器, 如:lesssassstylus,用来预编译sass或者less,减少了css代码的复用性。层级,mixin, 变量,循环, 函数等对编写以及开发UI组件都极为不便。

后处理器, 如: postCss,通常是在实现的样式表中依据css标准解决css,让其更加无效。目前最常做的是给css属性增加浏览器公有前缀,实现跨浏览器兼容性的问题。

css预处理器为css减少一些编程个性,无需思考浏览器的兼容问题,能够在CSS中应用变量,简略的逻辑程序,函数等在编程语言中的一些根本的性能,能够让css更加的简洁,减少适应性以及可读性,可维护性等。

其它css预处理器语言:Sass(Scss), Less, Stylus, Turbine, Swithch css, CSS Cacheer, DT Css

应用起因:

  • 构造清晰, 便于扩大
  • 能够很不便的屏蔽浏览器公有语法的差别
  • 能够轻松实现多重继承
  • 完满的兼容了CSS代码,能够利用到老我的项目中

—-问题知识点分割线—-

闭包产生的实质

以后环境中存在指向父级作用域的援用

—-问题知识点分割线—-

Sass、Less 是什么?为什么要应用他们?

他们都是 CSS 预处理器,是 CSS 上的一种形象层。他们是一种非凡的语法/语言编译成 CSS。 例如 Less 是一种动静款式语言,将 CSS 赋予了动静语言的个性,如变量,继承,运算, 函数,LESS 既能够在客户端上运行 (反对 IE 6+, Webkit, Firefox),也能够在服务端运行 (借助 Node.js)。

为什么要应用它们?

  • 构造清晰,便于扩大。 能够不便地屏蔽浏览器公有语法差别。封装对浏览器语法差别的反复解决, 缩小无意义的机械劳动。
  • 能够轻松实现多重继承。 齐全兼容 CSS 代码,能够不便地利用到老我的项目中。LESS 只是在 CSS 语法上做了扩大,所以老的 CSS 代码也能够与 LESS 代码一起编译。

—-问题知识点分割线—-

代码输入后果

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

输入后果如下:

1
Promise {<fulfilled>: undefined}

Promise.resolve办法的参数如果是一个原始值,或者是一个不具备then办法的对象,则Promise.resolve办法返回一个新的Promise对象,状态为resolved,Promise.resolve办法的参数,会同时传给回调函数。

then办法承受的参数是函数,而如果传递的并非是一个函数,它实际上会将其解释为then(null),这就会导致前一个Promise的后果会传递上面。

—-问题知识点分割线—-

Promise是什么,解决了什么,之前怎么实现的

    Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更正当和更弱小。
    解决来之前在申请中回调申请产生的回调天堂,使得当初的代码更加正当更加优雅,也更加容易定位查找问题。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理