当前位置: 首页 > news >正文

建立一个网站商城需要多久时间百度seo刷排名工具

建立一个网站商城需要多久时间,百度seo刷排名工具,wordpress 目录模板下载,做个网站得投入多少事件传播机制#xff08;事件流#xff09; 冒泡和捕获 谈一谈HTTP数据传输 大概遇到的情况就分为定长数据 与 不定长数据的处理吧。 定长数据 对于定长的数据包而言#xff0c;发送端在发送数据的过程中#xff0c;需要设置Content-Length,来指明发送数据的长度。 当…事件传播机制事件流 冒泡和捕获 谈一谈HTTP数据传输 大概遇到的情况就分为定长数据 与 不定长数据的处理吧。 定长数据 对于定长的数据包而言发送端在发送数据的过程中需要设置Content-Length,来指明发送数据的长度。 当然了如果采用了Gzip压缩的话Content-Length设置的就是压缩后的传输长度。 我们还需要知道的是 Content-Length如果存在并且有效的话则必须和消息内容的传输长度完全一致也就是说如果过短就会截断过长的话就会导致超时。如果采用短链接的话直接可以通过服务器关闭连接来确定消息的传输长度。那么在HTTP/1.0之前的版本中Content-Length字段可有可无,因为一旦服务器关闭连接我们就可以获取到传输数据的长度了。在HTTP/1.1版本中如果是Keep-alive的话chunked优先级高于Content-Length,若是非Keep-alive跟前面情况一样Content-Length可有可无。 那怎么来设置Content-Length 举个例子来看看 const server require(http).createServer(); server.on(request, (req, res) {if(req.url /index) {// 设置数据类型res.setHeader(Content-Type, text/plain);res.setHeader(Content-Length, 10);res.write(你好使用的是Content-Length设置传输数据形式);} })server.listen(3000, () {console.log(成功启动--TinaTian); })不定长数据 现在采用最多的就是HTTP/1.1版本来完成传输数据在保存Keep-alive状态下当数据是不定长的时候我们需要设置新的头部字段 Transfer-Encoding: chunked通过chunked机制可以完成对不定长数据的处理当然了你需要知道的是 如果头部信息中有Transfer-Encoding,优先采用Transfer-Encoding里面的方法来找到对应的长度。如果设置了Transfer-Encoding那么Content-Length将被忽视。使用长连接的话会持续的推送动态内容。 那我们来模拟一下吧 const server require(http).createServer(); server.on(request, (req, res) {if(req.url /index) {// 设置数据类型res.setHeader(Content-Type, text/html; charsetutf8);res.setHeader(Content-Length, 10);res.setHeader(Transfer-Encoding, chunked);res.write(你好使用的是Transfer-Encoding设置传输数据形式);setTimeout(() {res.write(第一次传输数据给您br/);}, 1000);res.write(骚等一下);setTimeout(() {res.write(第一次传输数据给您);res.end()}, 3000);} })server.listen(3000, () {console.log(成功启动--TinaTian); })上面使用的是nodejs中http模块有兴趣的小伙伴可以去试一试以上就是HTTP对定长数据和不定长数据传输过程中的处理手段。 transition和animation的区别 transition是过度属性强调过度它的实现需要触发一个事件比如鼠标移动上去焦点点击等才执行动画。它类似于flash的补间动画设置一个开始关键帧一个结束关键帧。animation是动画属性它的实现不需要触发事件设定好时间之后可以自己执行且可以循环一个动画。它也类似于flash的补间动画但是它可以设置多个关键帧用keyframe定义完成动画。 两栏布局的实现 一般两栏布局指的是左边一栏宽度固定右边一栏宽度自适应两栏布局的具体实现 利用浮动将左边元素宽度设置为200px并且设置向左浮动。将右边元素的margin-left设置为200px宽度设置为auto默认为auto撑满整个父元素。 .outer {height: 100px; } .left {float: left;width: 200px;background: tomato; } .right {margin-left: 200px;width: auto;background: gold; } 利用浮动左侧元素设置固定大小并左浮动右侧元素设置overflow: hidden; 这样右边就触发了BFCBFC的区域不会与浮动元素发生重叠所以两侧就不会发生重叠。 .left{width: 100px;height: 200px;background: red;float: left;}.right{height: 300px;background: blue;overflow: hidden;} 利用flex布局将左边元素设置为固定宽度200px将右边的元素设置为flex:1。 .outer {display: flex;height: 100px; } .left {width: 200px;background: tomato; } .right {flex: 1;background: gold; } 利用绝对定位将父级元素设置为相对定位。左边元素设置为absolute定位并且宽度设置为200px。将右边元素的margin-left的值设置为200px。 .outer {position: relative;height: 100px; } .left {position: absolute;width: 200px;height: 100px;background: tomato; } .right {margin-left: 200px;background: gold; } 利用绝对定位将父级元素设置为相对定位。左边元素宽度设置为200px右边元素设置为绝对定位左边定位为200px其余方向定位为0。 .outer {position: relative;height: 100px; } .left {width: 200px;background: tomato; } .right {position: absolute;top: 0;right: 0;bottom: 0;left: 200px;background: gold; } 使用 clear 属性清除浮动的原理 使用clear属性清除浮动其语法如下 clear:none|left|right|both 如果单看字面意思clear:left 是“清除左浮动”clear:right 是“清除右浮动”实际上这种解释是有问题的因为浮动一直还在并没有清除。 官方对clear属性解释“元素盒子的边不能和前面的浮动元素相邻”对元素设置clear属性是为了避免浮动元素对该元素的影响而不是清除掉浮动。 还需要注意 clear 属性指的是元素盒子的边不能和前面的浮动元素相邻注意这里“前面的”3个字也就是clear属性对“后面的”浮动元素是不闻不问的。考虑到float属性要么是left要么是right不可能同时存在同时由于clear属性对“后面的”浮动元素不闻不问因此当clear:left有效的时候clear:right必定无效也就是此时clear:left等同于设置clear:both同样地clear:right如果有效也是等同于设置clear:both。由此可见clear:left和clear:right这两个声明就没有任何使用的价值至少在CSS世界中是如此直接使用clear:both吧。 一般使用伪元素的方式清除浮动 .clear::after{ content:; display: block; clear:both;} clear属性只有块级元素才有效的而::after等伪元素默认都是内联水平这就是借助伪元素清除浮动影响时需要设置display属性值的原因。 画一条0.5px的线 采用transform: scale()的方式该方法用来定义元素的2D 缩放转换 transform: scale(0.5,0.5); 采用meta viewport的方式 meta nameviewport contentwidthdevice-width, initial-scale0.5, minimum-scale0.5, maximum-scale0.5/ 这样就能缩放到原来的0.5倍如果是1px那么就会变成0.5px。viewport只针对于移动端只在移动端上才能看到效果 参考 前端进阶面试题详细解答 数组的遍历方法有哪些 方法是否改变原数组特点forEach()否数组方法不改变原数组没有返回值map()否数组方法不改变原数组有返回值可链式调用filter()否数组方法过滤数组返回包含符合条件的元素的数组可链式调用for…of否for…of遍历具有Iterator迭代器的对象的属性返回的是数组的元素、对象的属性值不能遍历普通的obj对象将异步循环变成同步循环every() 和 some()否数组方法some()只要有一个是true便返回true而every()只要有一个是false便返回false.find() 和 findIndex()否数组方法find()返回的是第一个符合条件的值findIndex()返回的是第一个返回条件的值的索引值reduce() 和 reduceRight()否数组方法reduce()对数组正序操作reduceRight()对数组逆序操作 new操作符的实现原理 new操作符的执行过程 1首先创建了一个新的空对象 2设置原型将对象的原型设置为函数的 prototype 对象。 3让函数的 this 指向这个对象执行构造函数的代码为这个新对象添加属性 4判断函数的返回值类型如果是值类型返回创建的对象。如果是引用类型就返回这个引用类型的对象。 具体实现 function objectFactory() {let newObject null;let constructor Array.prototype.shift.call(arguments);let result null;// 判断参数是否是一个函数if (typeof constructor ! function) {console.error(type error);return;}// 新建一个空对象对象的原型为构造函数的 prototype 对象newObject Object.create(constructor.prototype);// 将 this 指向新建对象并执行函数result constructor.apply(newObject, arguments);// 判断返回对象let flag result (typeof result object || typeof result function);// 判断返回结果return flag ? result : newObject; } // 使用方法 objectFactory(构造函数, 初始化参数); 水平垂直居中的实现 利用绝对定位先将元素的左上角通过top:50%和left:50%定位到页面的中心然后再通过translate来调整元素的中心点到页面的中心。该方法需要考虑浏览器兼容问题。 .parent { position: relative;} .child { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);} 利用绝对定位设置四个方向的值都为0并将margin设置为auto由于宽高固定因此对应方向实现平分可以实现水平和垂直方向上的居中。该方法适用于盒子有宽高的情况 .parent {position: relative; }.child {position: absolute;top: 0;bottom: 0;left: 0;right: 0;margin: auto; } 利用绝对定位先将元素的左上角通过top:50%和left:50%定位到页面的中心然后再通过margin负值来调整元素的中心点到页面的中心。该方法适用于盒子宽高已知的情况 .parent {position: relative; }.child {position: absolute;top: 50%;left: 50%;margin-top: -50px; /* 自身 height 的一半 */margin-left: -50px; /* 自身 width 的一半 */ } 使用flex布局通过align-items:center和justify-content:center设置容器的垂直和水平方向上为居中对齐然后它的子元素也可以实现垂直和水平的居中。该方法要考虑兼容的问题该方法在移动端用的较多 .parent {display: flex;justify-content:center;align-items:center; } 常见的位运算符有哪些其计算规则是什么 现代计算机中数据都是以二进制的形式存储的即0、1两种状态计算机对二进制数据进行的运算加减乘除等都是叫位运算即将符号位共同参与运算的运算。 常见的位运算有以下几种 | 运算符 | 描述 | 运算规则 | | — | — | — | — | | | 与 | 两个位都为1时结果才为1 | | | | 或 | 两个位都为0时结果才为0 | | ^ | 异或 | 两个位相同为0相异为1 | | ~ | 取反 | 0变11变0 | | | 左移 | 各二进制位全部左移若干位高位丢弃低位补0 | | | 右移 | 各二进制位全部右移若干位正数左补0负数左补1右边丢弃 | 1. 按位与运算符 定义 参加运算的两个数据按二进制位进行“与”运算。 运算规则 0 0 0 0 1 0 1 0 0 1 1 1 总结两位同时为1结果才为1否则结果为0。 例如35 即 0000 0011 0000 0101 0000 0001 因此 35 的值为1。 注意负数按补码形式参加按位与运算。 用途 1判断奇偶 只要根据最未位是0还是1来决定为0就是偶数为1就是奇数。因此可以用if ((i 1) 0)代替if (i % 2 0)来判断a是不是偶数。 2清零 如果想将一个单元清零即使其全部二进制位为0只要与一个各位都为零的数值相与结果为零。 2. 按位或运算符| 定义 参加运算的两个对象按二进制位进行“或”运算。 运算规则 0 | 0 0 0 | 1 1 1 | 0 1 1 | 1 1 总结参加运算的两个对象只要有一个为1其值为1。 例如3|5即 0000 00110000 0101 0000 0111 因此3|5的值为7。 注意负数按补码形式参加按位或运算。 3. 异或运算符^ 定义 参加运算的两个数据按二进制位进行“异或”运算。 运算规则 0 ^ 0 0 0 ^ 1 1 1 ^ 0 1 1 ^ 1 0 总结参加运算的两个对象如果两个相应位相同为0相异为1。 例如3|5即 0000 00110000 0101 0000 0110 因此3^5的值为6。 异或运算的性质: 交换律(a^b)^c a^(b^c)结合律(a b)^c a^b b^c对于任何数x都有 x^x0x^0x自反性: a^b^ba^0a; 4. 取反运算符 (~) 定义 参加运算的一个数据按二进制进行“取反”运算。 运算规则 ~ 1 0~ 0 1 总结对一个二进制数按位取反即将0变11变0。 例如~6 即 0000 0110 1111 1001 在计算机中正数用原码表示负数使用补码存储首先看最高位最高位1表示负数0表示正数。此计算机二进制码为负数最高位为符号位。 当发现按位取反为负数时就直接取其补码变为十进制 0000 0110 1111 1001反码1000 0110补码1000 0111 因此~6的值为-7。 5. 左移运算符 定义 将一个运算对象的各二进制位全部左移若干位左边的二进制位丢弃右边补0。 设 a1010 1110a a 2 将a的二进制位左移2位、右补0即得a1011 1000。 若左移时舍弃的高位不包含1则每左移一位相当于该数乘以2。 6. 右移运算符 定义 将一个数的各二进制位全部右移若干位正数左补0负数左补1右边丢弃。 例如aa2 将a的二进制位右移2位左补0 或者 左补1得看被移数是正还是负。 操作数每右移一位相当于该数除以2。 7. 原码、补码、反码 上面提到了补码、反码等知识这里就补充一下。 计算机中的有符号数有三种表示方法即原码、反码和补码。三种表示方法均有符号位和数值位两部分符号位都是用0表示“正”用1表示“负”而数值位三种表示方法各不相同。 1原码 原码就是一个数的二进制数。例如10的原码为0000 1010 2反码 正数的反码与原码相同如10 反码为 0000 1010负数的反码为除符号位按位取反即0变11变0。 例如-10 原码1000 1010 反码1111 0101 3补码 正数的补码与原码相同如10 补码为 0000 1010负数的补码是原码除符号位外的所有位取反即0变11变0然后加1也就是反码加1。 例如-10 原码1000 1010 反码1111 0101 补码1111 0110 JavaScript为什么要进行变量提升它导致了什么问题 变量提升的表现是无论在函数中何处位置声明的变量好像都被提升到了函数的首部可以在变量声明前访问到而不会报错。 造成变量声明提升的本质原因是 js 引擎在代码执行前有一个解析的过程创建了执行上下文初始化了一些代码执行时需要用到的对象。当访问一个变量时会到当前执行上下文中的作用域链中去查找而作用域链的首端指向的是当前执行上下文的变量对象这个变量对象是执行上下文的一个属性它包含了函数的形参、所有的函数和变量声明这个对象的是在代码解析的时候创建的。 首先要知道JS在拿到一个变量或者一个函数的时候会有两步操作即解析和执行。 在解析阶段JS会检查语法并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境先把代码中即将执行的变量、函数声明都拿出来变量先赋值为undefined函数先声明好可使用。在一个函数执行之前也会创建一个函数执行上下文环境跟全局执行上下文类似不过函数执行上下文会多出this、arguments和函数的参数。 全局上下文变量定义函数声明函数上下文变量定义函数声明thisarguments 在执行阶段就是按照代码的顺序依次执行。 那为什么会进行变量提升呢主要有以下两个原因 提高性能容错性更好 1提高性能 在JS代码执行之前会进行语法检查和预编译并且这一操作只进行一次。这么做就是为了提高性能如果没有这一步那么每次执行代码前都必须重新解析一遍该变量函数而这是没有必要的因为变量函数的代码并不会改变解析一遍就够了。 在解析的过程中还会为函数生成预编译代码。在预编译时会统计声明了哪些变量、创建了哪些函数并对函数的代码进行压缩去除注释、不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间不需要再解析一遍去获取代码中声明了哪些变量创建了哪些函数并且因为代码压缩的原因代码执行也更快了。 2容错性更好 变量提升可以在一定程度上提高JS的容错性看下面的代码 a 1;var a;console.log(a); 如果没有变量提升这两行代码就会报错但是因为有了变量提升这段代码就可以正常执行。 虽然在可以开发过程中可以完全避免这样写但是有时代码很复杂的时候。可能因为疏忽而先使用后定义了这样也不会影响正常使用。由于变量提升的存在而会正常运行。 总结 解析和预编译过程中的声明提升可以提高性能让函数可以在执行时预先为变量分配栈空间声明提升还可以提高JS代码的容错性使一些不规范的代码也可以正常执行 变量提升虽然有一些优点但是他也会造成一定的问题在ES6中提出了let、const来定义变量它们就没有变量提升的机制。下面看一下变量提升可能会导致的问题 var tmp new Date();function fn(){console.log(tmp);if(false){var tmp hello world;} }fn(); // undefined 在这个函数中原本是要打印出外层的tmp变量但是因为变量提升的问题内层定义的tmp被提到函数内部的最顶部相当于覆盖了外层的tmp所以打印结果为undefined。 var tmp hello world;for (var i 0; i tmp.length; i) {console.log(tmp[i]); }console.log(i); // 11 由于遍历时定义的i会变量提升成为一个全局变量在函数结束之后不会被销毁所以打印出来11。 对象继承的方式有哪些 1第一种是以原型链的方式来实现继承但是这种实现方式存在的缺点是在包含有引用类型的数据时会被所有的实例对象所共享容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。 2第二种方式是使用借用构造函数的方式这种方式是通过在子类型的函数中调用超类型的构造函数来实现的这一种方法解决了不能向超类型传递参数的缺点但是它存在的一个问题就是无法实现函数方法的复用并且超类型原型定义的方法子类型也没有办法访问到。 3第三种方式是组合继承组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题但是由于我们是以超类型的实例来作为子类型的原型所以调用了两次超类的构造函数造成了子类型的原型中多了很多不必要的属性。 4第四种方式是原型式继承原型式继承的主要思路就是基于已有的对象来创建新的对象实现的原理是向函数中传入一个对象然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型只是对某个对象实现一种简单继承ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。 5第五种方式是寄生式继承寄生式继承的思路是创建一个用于封装继承过程的函数通过传入一个对象然后复制一个对象的副本然后对象进行扩展最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承如果这个对象不是自定义类型时。缺点是没有办法实现函数的复用。 6第六种方式是寄生式组合继承组合继承的缺点就是使用超类型的实例做为子类型的原型导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型这样就避免了创建不必要的属性。 HTTP和HTTPS协议的区别 HTTP和HTTPS协议的主要区别如下 HTTPS协议需要CA证书费用较高而HTTP协议不需要HTTP协议是超文本传输协议信息是明文传输的HTTPS则是具有安全性的SSL加密传输协议使用不同的连接方式端口也不同HTTP协议端口是80HTTPS协议端口是443HTTP协议连接很简单是无状态的HTTPS协议是有SSL和HTTP协议构建的可进行加密传输、身份认证的网络协议比HTTP更加安全。 Promise.all和Promise.race的区别的使用场景 1Promise.all Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时成功和失败的返回值是不同的成功的时候返回的是一个结果数组而失败的时候则返回最先被reject失败状态的值。 Promise.all中传入的是数组返回的也是是数组并且会将进行映射传入的promise对象返回的值是按照顺序在数组中排列的但是注意的是他们执行的顺序并不是按照顺序的除非可迭代对象为空。 需要注意Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的这样当遇到发送多个请求并根据请求顺序获取和使用数据的场景就可以使用Promise.all来解决。 2Promise.race 顾名思义Promse.race就是赛跑的意思意思就是说Promise.race([p1, p2, p3])里面哪个结果获得的快就返回那个结果不管结果本身是成功状态还是失败状态。当要做一件事超过多长时间就不做了可以用这个方法来解决 Promise.race([promise1,timeOutPromise(5000)]).then(res{}) POST和PUT请求的区别 PUT请求是向服务器端发送数据从而修改数据的内容但是不会增加数据的种类等也就是说无论进行多少次PUT操作其结果并没有不同。可以理解为时更新数据POST请求是向服务器端发送数据该请求会改变数据的种类等资源它会创建新的内容。可以理解为是创建数据 Compositon api Composition API也叫组合式API是Vue3.x的新特性。 通过创建 Vue 组件我们可以将接口的可重复部分及其功能提取到可重用的代码段中。仅此一项就可以使我们的应用程序在可维护性和灵活性方面走得更远。然而我们的经验已经证明光靠这一点可能是不够的尤其是当你的应用程序变得非常大的时候——想想几百个组件。在处理如此大的应用程序时共享和重用代码变得尤为重要 Vue2.0中随着功能的增加组件变得越来越复杂越来越难维护而难以维护的根本原因是Vue的API设计迫使开发者使用watchcomputedmethods选项组织代码而不是实际的业务逻辑。另外Vue2.0缺少一种较为简洁的低成本的机制来完成逻辑复用虽然可以minxis完成逻辑复用但是当mixin变多的时候会使得难以找到对应的data、computed或者method来源于哪个mixin使得类型推断难以进行。所以Composition API的出现主要是也是为了解决Option API带来的问题第一个是代码组织问题Compostion API可以让开发者根据业务逻辑组织自己的代码让代码具备更好的可读性和可扩展性也就是说当下一个开发者接触这一段不是他自己写的代码时他可以更好的利用代码的组织反推出实际的业务逻辑或者根据业务逻辑更好的理解代码。第二个是实现代码的逻辑提取与复用当然mixin也可以实现逻辑提取与复用但是像前面所说的多个mixin作用在同一个组件时很难看出property是来源于哪个mixin来源不清楚另外多个mixin的property存在变量命名冲突的风险。而Composition API刚好解决了这两个问题。 通俗的讲 没有Composition API之前vue相关业务的代码需要配置到option的特定的区域中小型项目是没有问题的但是在大型项目中会导致后期的维护性比较复杂同时代码可复用性不高。Vue3.x中的composition-api就是为了解决这个问题而生的 compositon api提供了以下几个函数 setuprefreactivewatchEffectwatchcomputedtoRefs生命周期的hooks 都说Composition API与React Hook很像说说区别 从React Hook的实现角度看React Hook是根据useState调用的顺序来确定下一次重渲染时的state是来源于哪个useState所以出现了以下限制 不能在循环、条件、嵌套函数中调用Hook必须确保总是在你的React函数的顶层调用HookuseEffect、useMemo等函数必须手动确定依赖关系 而Composition API是基于Vue的响应式系统实现的与React Hook的相比 声明在setup函数内一次组件实例化只调用一次setup而React Hook每次重渲染都需要调用Hook使得React的GC比Vue更有压力性能也相对于Vue来说也较慢Compositon API的调用不需要顾虑调用顺序也可以在循环、条件、嵌套函数中使用响应式系统自动实现了依赖收集进而组件的部分的性能优化由Vue内部自己完成而React Hook需要手动传入依赖而且必须必须保证依赖的顺序让useEffect、useMemo等函数正确的捕获依赖变量否则会由于依赖不正确使得组件性能下降。 虽然Compositon API看起来比React Hook好用但是其设计思想也是借鉴React Hook的。 watch 的理解 watch没有缓存性更多的是观察的作用可以监听某些数据执行回调。当我们需要深度监听对象中的属性时可以打开deeptrue选项这样便会对对象中的每一项进行监听。这样会带来性能问题优化的话可以使用字符串形式监听 注意Watcher : 观察者对象 , 实例分为渲染 watcher (render watcher),计算属性 watcher (computed watcher),侦听器 watcheruser watcher三种 React Fiber架构 最主要的思想就是将任务拆分 。 DOM需要渲染时暂停空闲时恢复。window.requestIdleCallbackReact内部实现的机制 React 追求的是 “快速响应”那么“快速响应“的制约因素都有什么呢 CPU的瓶颈当项目变得庞大、组件数量繁多、遇到大计算量的操作或者设备性能不足使得页面掉帧导致卡顿。IO的瓶颈发送网络请求后由于需要等待数据返回才能进一步操作导致不能快速响应。 fiber 架构主要就是用来解决 CPU 和网络的问题这两个问题一直也是最影响前端开发体验的地方一个会造成卡顿一个会造成白屏。为此 react 为前端引入了两个新概念Time Slicing 时间分片和Suspense。 1. React 都做过哪些优化 React渲染页面的两个阶段 调度阶段reconciliation在这个阶段 React 会更新数据生成新的 Virtual DOM然后通过Diff算法快速找出需要更新的元素放到更新队列中去得到新的更新队列。渲染阶段commit这个阶段 React 会遍历更新队列将其所有的变更一次性更新到DOM上 React 15 架构 React15架构可以分为两层 Reconciler协调器—— 负责找出变化的组件Renderer渲染器—— 负责将变化的组件渲染到页面上 在React15及以前Reconciler采用递归的方式创建虚拟DOM递归过程是不能中断的。如果组件树的层级很深递归会占用线程很多时间递归更新时间超过了16ms用户交互就会卡顿。为了解决这个问题React16将递归的无法中断的更新重构为异步的可中断更新由于曾经用于递归的虚拟DOM数据结构已经无法满足需要。于是全新的Fiber架构应运而生。 React 16 架构 为了解决同步更新长时间占用线程导致页面卡顿的问题也为了探索运行时优化的更多可能React开始重构并一直持续至今。重构的目标是实现Concurrent Mode并发模式。从v15到v16React团队花了两年时间将源码架构中的Stack Reconciler重构为Fiber ReconcilerReact16架构可以分为三层 Scheduler调度器—— 调度任务的优先级高优任务优先进入ReconcilerReconciler协调器—— 负责找出变化的组件更新工作从递归变成了可以中断的循环过程。Reconciler内部采用了Fiber的架构Renderer渲染器—— 负责将变化的组件渲染到页面上。 React 17 优化 使用Lane来管理任务的优先级。Lane用二进制位表示任务的优先级方便优先级的计算位运算不同优先级占用不同位置的“赛道”而且存在批的概念优先级越低“赛道”越多。高优先级打断低优先级新建的任务需要赋予什么优先级等问题都是Lane所要解决的问题。Concurrent Mode的目的是实现一套可中断/恢复的更新机制。其由两部分组成 一套协程架构Fiber Reconciler基于协程架构的启发式更新算法控制协程架构工作方式的算法 2. 浏览器一帧都会干些什么以及requestIdleCallback的启示 我们都知道页面的内容都是一帧一帧绘制出来的浏览器刷新率代表浏览器一秒绘制多少帧。原则上说 1s 内绘制的帧数也多画面表现就也细腻。目前浏览器大多是 60Hz60帧/s每一帧耗时也就是在 16.6ms 左右。那么在这一帧的16.6ms 过程中浏览器又干了些什么呢 通过上面这张图可以清楚的知道浏览器一帧会经过下面这几个过程 接受输入事件执行事件回调开始一帧执行 RAF (RequestAnimationFrame)页面布局样式计算绘制渲染执行 RIC (RequestIdelCallback) 第七步的 RIC 事件不是每一帧结束都会执行只有在一帧的 16.6ms 中做完了前面 6 件事儿且还有剩余时间才会执行。如果一帧执行结束后还有时间执行 RIC 事件那么下一帧需要在事件执行结束才能继续渲染所以 RIC 执行不要超过 30ms如果长时间不将控制权交还给浏览器会影响下一帧的渲染导致页面出现卡顿和事件响应不及时。 requestIdleCallback 的启示我们以浏览器是否有剩余时间作微任务中断的标准那么我们需要一种机制当浏览器有剩余时间时通知我们。 requestIdleCallback((deadline) { // deadline 有两个参数// timeRemaining(): 当前帧还剩下多少时间// didTimeout: 是否超时 // 另外 requestIdleCallback 后如果跟上第二个参数 {timeout: ...} 则会强制浏览器在当前帧执行完后执行。if (deadline.timeRemaining() 0) {// TODO} else {requestIdleCallback(otherTasks);} });// 用法示例 var tasksNum 10000requestIdleCallback(unImportWork)function unImportWork(deadline) {while (deadline.timeRemaining() tasksNum 0) {console.log(执行了${10000 - tasksNum 1}个任务)tasksNum--}if (tasksNum 0) { // 在未来的帧中继续执行requestIdleCallback(unImportWork)} }其实部分浏览器已经实现了这个API这就是requestIdleCallback。但是由于以下因素Facebook 抛弃了 requestIdleCallback的原生 API 浏览器兼容性触发频率不稳定受很多因素影响。比如当我们的浏览器切换tab后之前tab注册的requestIdleCallback触发的频率会变得很低。 基于以上原因在React中实现了功能更完备的requestIdleCallbackpolyfill这就是Scheduler。除了在空闲时触发回调的功能外Scheduler还提供了多种调度优先级供任务设置 3. React Fiber是什么 React Fiber是对核心算法的一次重新实现。React Fiber把更新过程碎片化把一个耗时长的任务分成很多小片每一个小片的运行时间很短虽然总时间依然很长但是在每个小片执行完之后都给其他任务一个执行的机会这样唯一的线程就不会被独占其他任务依然有运行的机会 在React Fiber中一次更新过程会分成多个分片完成所以完全有可能一个更新任务还没有完成就被另一个更高优先级的更新过程打断这时候优先级高的更新任务会优先处理完而低优先级更新任务所做的工作则会完全作废然后等待机会重头再来因为一个更新过程可能被打断所以React Fiber一个更新过程被分为两个阶段(Phase)第一个阶段Reconciliation Phase和第二阶段Commit Phase在第一阶段Reconciliation PhaseReact Fiber会找出需要更新哪些DOM这个阶段是可以被打断的但是到了第二阶段Commit Phase那就一鼓作气把DOM更新完绝不会被打断这两个阶段大部分工作都是React Fiber做和我们相关的也就是生命周期函数 React Fiber改变了之前react的组件渲染机制新的架构使原来同步渲染的组件现在可以异步化可中途中断渲染执行更高优先级的任务。释放浏览器主线程 关键特性 增量渲染把渲染任务拆分成块匀到多帧更新时能够暂停终止复用渲染任务给不同类型的更新赋予优先级并发方面新的基础能力 增量渲染用来解决掉帧的问题渲染任务拆分之后每次只做一小段做完一段就把时间控制权交还给主线程而不像之前长时间占用 4. 组件的渲染顺序 假如有A,B,C,D组件层级结构为 我们知道组件的生命周期为 挂载阶段 constructor()componentWillMount()render()componentDidMount() 更新阶段为 componentWillReceiveProps()shouldComponentUpdate()componentWillUpdate()render()componentDidUpdate 那么在挂载阶段A,B,C,D的生命周期渲染顺序是如何的呢 那么在挂载阶段A,B,C,D的生命周期渲染顺序是如何的呢 以render()函数为分界线。从顶层组件开始一直往下直至最底层子组件。然后再往上 组件update阶段同理 前面是react16以前的组建渲染方式。这就存在一个问题 如果这是一个很大层级很深的组件react渲染它需要几十甚至几百毫秒在这期间react会一直占用浏览器主线程任何其他的操作包括用户的点击鼠标移动等操作都无法执行 Fiber架构就是为了解决这个问题 看一下fiber架构 组建的渲染顺序 加入fiber的react将组件更新分为两个时期 这两个时期以render为分界 render前的生命周期为phase1,render后的生命周期为phase2 phase1的生命周期是可以被打断的每隔一段时间它会跳出当前渲染进程去确定是否有其他更重要的任务。此过程React在 workingProgressTree 并不是真实的virtualDomTree上复用 current 上的 Fiber 数据结构来一步地通过requestIdleCallback来构建新的 tree标记处需要更新的节点放入队列中phase2的生命周期是不可被打断的React 将其所有的变更一次性更新到DOM上 这里最重要的是phase1这是时期所做的事。因此我们需要具体了解phase1的机制 如果不被打断那么phase1执行完会直接进入render函数构建真实的virtualDomTree如果组件再phase1过程中被打断即当前组件只渲染到一半也许是在willMount,也许是willUpdate~反正是在render之前的生命周期那么react会怎么干呢 react会放弃当前组件所有干到一半的事情去做更高优先级更重要的任务当然也可能是用户鼠标移动或者其他react监听之外的任务当所有高优先级任务执行完之后react通过callback回到之前渲染到一半的组件从头开始渲染。看起来放弃已经渲染完的生命周期会有点不合理反而会增加渲染时长但是react确实是这么干的 所有phase1的生命周期函数都可能被执行多次因为可能会被打断重来 这样的话就和react16版本之前有很大区别了因为可能会被执行多次那么我们最好就得保证phase1的生命周期每一次执行的结果都是一样的否则就会有问题因此最好都是纯函数 如果高优先级的任务一直存在那么低优先级的任务则永远无法进行组件永远无法继续渲染。这个问题facebook目前好像还没解决所以facebook在react16增加fiber结构其实并不是为了减少组件的渲染时间事实上也并不会减少最重要的是现在可以使得一些更高优先级的任务如用户的操作能够优先执行提高用户的体验至少用户不会感觉到卡顿 5 React Fiber架构总结 React Fiber如何性能优化 更新的两个阶段 调度算法阶段-执行diff算法纯js计算Commit阶段-将diff结果渲染dom 可能会有性能问题 JS是单线程的且和DOM渲染公用一个线程当组件足够复杂组件更新时计算和渲染压力都大同时再有DOM操作需求动画、鼠标拖拽等将卡顿 解决方案fiber 将调度算法阶段阶段任务拆分Commit无法拆分DOM需要渲染时暂停空闲时恢复分散执行: 任务分割后就可以把小任务单元分散到浏览器的空闲期间去排队执行而实现的关键是两个新API: requestIdleCallback 与 requestAnimationFrame 低优先级的任务交给requestIdleCallback处理这是个浏览器提供的事件循环空闲期的回调函数需要 pollyfill而且拥有 deadline 参数限制执行事件以继续切分任务高优先级的任务交给requestAnimationFrame处理 React 的核心流程可以分为两个部分: reconciliation (调度算法也可称为 render) 更新 state 与 props调用生命周期钩子生成 virtual dom 这里应该称为 Fiber Tree 更为符合 通过新旧 vdom 进行 diff 算法获取 vdom change确定是否需要重新渲染 commit 如需要则操作 dom 节点更新 要了解 Fiber我们首先来看为什么需要它 问题 : 随着应用变得越来越庞大整个更新渲染的过程开始变得吃力大量的组件渲染会导致主进程长时间被占用导致一些动画或高频操作出现卡顿和掉帧的情况。而关键点便是 同步阻塞。在之前的调度算法中React 需要实例化每个类组件生成一颗组件树使用 同步递归 的方式进行遍历渲染而这个过程最大的问题就是无法 暂停和恢复。解决方案: 解决同步阻塞的方法通常有两种: 异步 与 任务分割。而 React Fiber 便是为了实现任务分割而诞生的简述 在 React V16 将调度算法进行了重构 将之前的 stack reconciler 重构成新版的 fiber reconciler变成了具有链表和指针的 单链表树遍历算法。通过指针映射每个单元都记录着遍历当下的上一步与下一步从而使遍历变得可以被暂停和重启这里我理解为是一种 任务分割调度算法主要是 将原先同步更新渲染的任务分割成一个个独立的 小任务单位根据不同的优先级将小任务分散到浏览器的空闲时间执行充分利用主进程的事件循环机制 核心 Fiber 这里可以具象为一个 数据结构 class Fiber {constructor(instance) {this.instance instance// 指向第一个 child 节点this.child child// 指向父节点this.return parent// 指向第一个兄弟节点this.sibling previous} }链表树遍历算法 : 通过 节点保存与映射便能够随时地进行 停止和重启这样便能达到实现任务分割的基本前提 首先通过不断遍历子节点到树末尾开始通过 sibling 遍历兄弟节点return 返回父节点继续执行2直到 root 节点后跳出遍历 任务分割 React 中的渲染更新可以分成两个阶段 reconciliation 阶段 : vdom 的数据对比是个适合拆分的阶段比如对比一部分树后先暂停执行个动画调用待完成后再回来继续比对Commit 阶段 : 将 change list 更新到 dom 上并不适合拆分才能保持数据与 UI 的同步。否则可能由于阻塞 UI 更新而导致数据更新和 UI 不一致的情况 分散执行: 任务分割后就可以把小任务单元分散到浏览器的空闲期间去排队执行而实现的关键是两个新API: requestIdleCallback 与 requestAnimationFrame 低优先级的任务交给requestIdleCallback处理这是个浏览器提供的事件循环空闲期的回调函数需要 pollyfill而且拥有 deadline 参数限制执行事件以继续切分任务高优先级的任务交给requestAnimationFrame处理 // 类似于这样的方式 requestIdleCallback((deadline) {// 当有空闲时间时我们执行一个组件渲染// 把任务塞到一个个碎片时间中去while ((deadline.timeRemaining() 0 || deadline.didTimeout) nextComponent) {nextComponent performWork(nextComponent);} });优先级策略: 文本框输入 本次调度结束需完成的任务 动画过渡 交互反馈 数据更新 不会显示但以防将来会显示的任务 Fiber 其实可以算是一种编程思想在其它语言中也有许多应用(Ruby Fiber)。核心思想是 任务拆分和协同主动把执行权交给主线程使主线程有时间空挡处理其他高优先级任务。当遇到进程阻塞的问题时任务分割、异步调用 和 缓存策略 是三个显著的解决思路。 OSI七层模型 ISO为了更好的使网络应用更为普及推出了OSI参考模型。 1应用层 OSI参考模型中最靠近用户的一层是为计算机用户提供应用接口也为用户直接提供各种网络服务。我们常见应用层的网络服务协议有HTTPHTTPSFTPPOP3、SMTP等。 在客户端与服务器中经常会有数据的请求这个时候就是会用到http(hyper text transfer protocol)(超文本传输协议)或者https.在后端设计数据接口时我们常常使用到这个协议。FTP是文件传输协议在开发过程中个人并没有涉及到但是我想在一些资源网站比如百度网盘迅雷应该是基于此协议的。SMTP是simple mail transfer protocol简单邮件传输协议。在一个项目中在用户邮箱验证码登录的功能时使用到了这个协议。 2表示层 表示层提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别。如果必要该层可提供一种标准表示形式用于将计算机内部的多种数据格式转换成通信中采用的标准表示形式。数据压缩和加密也是表示层可提供的转换功能之一。 在项目开发中为了方便数据传输可以使用base64对数据进行编解码。如果按功能来划分base64应该是工作在表示层。 3会话层 会话层就是负责建立、管理和终止表示层实体之间的通信会话。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。 4传输层 传输层建立了主机端到端的链接传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。我们通常说的TCP UDP就是在这一层。端口号既是这里的“端”。 5网络层 本层通过IP寻址来建立两个节点之间的连接为源端的运输层送来的分组选择合适的路由和交换节点正确无误地按照地址传送给目的端的运输层。就是通常说的IP层。这一层就是我们经常说的IP协议层。IP协议是Internet的基础。我们可以这样理解网络层规定了数据包的传输路线而传输层则规定了数据包的传输方式。 6数据链路层 将比特组合成字节,再将字节组合成帧,使用链路层地址 (以太网使用MAC地址)来访问介质,并进行差错检测。 网络层与数据链路层的对比通过上面的描述我们或许可以这样理解网络层是规划了数据包的传输路线而数据链路层就是传输路线。不过在数据链路层上还增加了差错控制的功能。 7物理层 实际最终信号的传输是通过物理层实现的。通过物理介质传输比特流。规定了电平、速度和电缆针脚。常用设备有各种物理设备集线器、中继器、调制解调器、网线、双绞线、同轴电缆。这些都是物理层的传输介质。 OSI七层模型通信特点对等通信 对等通信为了使数据分组从源传送到目的地源端OSI模型的每一层都必须与目的端的对等层进行通信这种通信方式称为对等层通信。在每一层通信过程中使用本层自己协议进行通信。 JSX语法糖本质 JSX是语法糖通过babel转成React.createElement函数在babel官网上可以在线把JSX转成React的JS语法 首先解析出来的话就是一个createElement函数然后这个函数执行完后会返回一个vnode通过vdom的patch或者是其他的一个方法最后渲染一个页面 script标签中不添加text/babel解析jsx语法的情况下 scriptconst ele React.createElement(h2, null, Hello React!);ReactDOM.render(ele, document.getElementById(app)); /scriptJSX的本质是React.createElement()函数 createElement函数返回的对象是ReactEelement对象。 createElement的写法如下 class App extends React.Component {constructor() {super()this.state {}}render() {return React.createElement(div, null,/*第一个子元素header*/React.createElement(div, { className: header },React.createElement(h1, { title: \u6807\u9898 }, \u6211\u662F\u6807\u9898)),/*第二个子元素content*/React.createElement(div, { className: content },React.createElement(h2, null, \u6211\u662F\u9875\u9762\u7684\u5185\u5BB9),React.createElement(button, null, \u6309\u94AE),React.createElement(button, null, 1),React.createElement(a, { href: http://www.baidu.com },\u767E\u5EA6\u4E00\u4E0B)),/*第三个子元素footer*/React.createElement(div, { className: footer },React.createElement(p, null, \u6211\u662F\u5C3E\u90E8\u7684\u5185\u5BB9)));} }ReactDOM.render(App /, document.getElementById(app));实际开发中不会使用createElement来创建ReactElement的一般都是使用JSX的形式开发。 ReactElement在程序中打印一下 render() {let ele (divdiv classNameheaderh1 title标题我是标题/h1/divdiv classNamecontenth2我是页面的内容/h2button按钮/buttonbutton1/buttona hrefhttp://www.baidu.com百度一下/a/divdiv classNamefooterp我是尾部的内容/p/div/div)console.log(ele);return ele; }react通过babel把JSX转成createElement函数生成ReactElement对象然后通过ReactDOM.render函数把ReactElement渲染成真实的DOM元素 为什么 React 使用 JSX 在回答问题之前我首先解释下什么是 JSX 吧。JSX 是一个 JavaScript 的语法扩展结构类似 XML。JSX 主要用于声明 React 元素但 React 中并不强制使用 JSX。即使使用了 JSX也会在构建过程中通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖接下来与 JSX 以外的三种技术方案进行对比 首先是模板React 团队认为模板不应该是开发过程中的关注点因为引入了模板语法、模板指令等概念是一种不佳的实现方案其次是模板字符串模板字符串编写的结构会造成多次内部嵌套使整个结构变得复杂并且优化代码提示也会变得困难重重所以 React 最后选用了 JSX因为 JSX 与其设计思想贴合不需要引入过多新的概念对编辑器的代码提示也极为友好。 Babel 插件如何实现 JSX 到 JS 的编译 在 React 面试中这个问题很容易被追问也经常被要求手写。 它的实现原理是这样的。Babel 读取代码并解析生成 AST再将 AST 传入插件层进行转换在转换时就可以将 JSX 的结构转换为 React.createElement 的函数。如下代码所示 module.exports function (babel) {var t babel.types;return {name: custom-jsx-plugin,visitor: {JSXElement(path) {var openingElement path.node.openingElement;var tagName openingElement.name.name;var args []; args.push(t.stringLiteral(tagName)); var attribs t.nullLiteral(); args.push(attribs); var reactIdentifier t.identifier(React); //objectvar createElementIdentifier t.identifier(createElement); var callee t.memberExpression(reactIdentifier, createElementIdentifier)var callExpression t.callExpression(callee, args);callExpression.arguments callExpression.arguments.concat(path.node.children);path.replaceWith(callExpression, path.node); },},}; };React.createElement源码分析 /**101. React的创建元素方法*/ export function createElement(type, config, children) {// propName 变量用于储存后面需要用到的元素属性let propName; // props 变量用于储存元素属性的键值对集合const props {}; // key、ref、self、source 均为 React 元素的属性此处不必深究let key null;let ref null; let self null; let source null; // config 对象中存储的是元素的属性if (config ! null) { // 进来之后做的第一件事是依次对 ref、key、self 和 source 属性赋值if (hasValidRef(config)) {ref config.ref;}// 此处将 key 值字符串化if (hasValidKey(config)) {key config.key; }self config.__self undefined ? null : config.__self;source config.__source undefined ? null : config.__source;// 接着就是要把 config 里面的属性都一个一个挪到 props 这个之前声明好的对象里面for (propName in config) {if (// 筛选出可以提进 props 对象里的属性hasOwnProperty.call(config, propName) !RESERVED_PROPS.hasOwnProperty(propName) ) {props[propName] config[propName]; }}}// childrenLength 指的是当前元素的子元素的个数减去的 2 是 type 和 config 两个参数占用的长度const childrenLength arguments.length - 2; // 如果抛去type和config就只剩下一个参数一般意味着文本节点出现了if (childrenLength 1) { // 直接把这个参数的值赋给props.childrenprops.children children; // 处理嵌套多个子元素的情况} else if (childrenLength 1) { // 声明一个子元素数组const childArray Array(childrenLength); // 把子元素推进数组里for (let i 0; i childrenLength; i) { childArray[i] arguments[i 2];}// 最后把这个数组赋值给props.childrenprops.children childArray; } // 处理 defaultPropsif (type type.defaultProps) {const defaultProps type.defaultProps;for (propName in defaultProps) { if (props[propName] undefined) {props[propName] defaultProps[propName];}}}// 最后返回一个调用ReactElement执行方法并传入刚才处理过的参数return ReactElement(type,key,ref,self,source,ReactCurrentOwner.current,props,); }入参解读创造一个元素需要知道哪些信息 export function createElement(type, config, children)createElement 有 3 个入参这 3 个入参囊括了 React 创建一个元素所需要知道的全部信息。 type用于标识节点的类型。它可以是类似“h1”“div”这样的标准 HTML 标签字符串也可以是 React 组件类型或 React fragment 类型。config以对象形式传入组件所有的属性都会以键值对的形式存储在 config 对象中。children以对象形式传入它记录的是组件标签之间嵌套的内容也就是所谓的“子节点”“子元素” React.createElement(ul, {// 传入属性键值对className: list// 从第三个入参开始往后传入的参数都是 children }, React.createElement(li, {key: 1 }, 1), React.createElement(li, {key: 2 }, 2));这个调用对应的 DOM 结构如下 ul classNamelistli key11/lili key22/li /ulcreateElement 函数体拆解 createElement 中并没有十分复杂的涉及算法或真实 DOM 的逻辑它的每一个步骤几乎都是在格式化数据。 现在看来createElement 原来只是个“参数中介”。此时我们的注意力自然而然地就聚焦在了 ReactElement 上 出参解读初识虚拟 DOM createElement 执行到最后会 return 一个针对 ReactElement 的调用。这里关于 ReactElement我依然先给出源码 注释形式的解析 const ReactElement function(type, key, ref, self, source, owner, props) {const element {// REACT_ELEMENT_TYPE是一个常量用来标识该对象是一个ReactElement$$typeof: REACT_ELEMENT_TYPE,// 内置属性赋值type: type,key: key,ref: ref,props: props,// 记录创造该元素的组件_owner: owner,};// if (__DEV__) {// 这里是一些针对 __DEV__ 环境下的处理对于大家理解主要逻辑意义不大此处我直接省略掉以免混淆视听}return element; };ReactElement 其实只做了一件事情那就是“创建”说得更精确一点是“组装”ReactElement 把传入的参数按照一定的规范“组装”进了 element 对象里并把它返回给了 eact.createElement最终 React.createElement 又把它交回到了开发者手中 const AppJSX (div classNameApph1 classNametitleI am the title/h1p classNamecontentI am the content/p /div)console.log(AppJSX)你会发现它确实是一个标准的 ReactElement 对象实例 这个 ReactElement 对象实例本质上是以 JavaScript 对象形式存在的对 DOM 的描述也就是老生常谈的“虚拟 DOM”准确地说是虚拟 DOM 中的一个节点)
http://www.w-s-a.com/news/147415/

相关文章:

  • 网站建设网站及上传wordpress火车头发布
  • 有没有做网站的团队电脑版传奇网站
  • 建立企业网站公司医疗创意小产品设计
  • 深圳 做网站 车公庙免费的招标网有哪些
  • 网站在那里备案成都成华区网站建设
  • 做网站选哪家好搜索引擎优化的目标体系包括哪些
  • 做数据可视化的网站ppt2016是制作网页的软件
  • 济宁市建设工程质量监督站网站徐州网站优化推广
  • 北京网站设计多少钱php做商品网站
  • 能打开的网站你了解的彩票网站开发dadi163
  • 手机做网站价格优秀企业网站建设价格
  • 电商网站建设企业做网站的客户多吗
  • 有做思维图的网站吗西安建设市场诚信信息平台网站
  • 网站建设求职具备什么30岁学网站开发
  • 官方网站minecraft北京低价做网站
  • 网站建设报价兴田德润机械加工网络接单
  • 免费的推广网站安卓app制作平台
  • 长春火车站附近美食建设信用卡银行积分兑换商城网站
  • 网站提交网址如何备份wordpress网页
  • 龙腾盛世网站建设医院管理系统
  • 网站切换图片做背景怎么写外贸营销邮件主题一般怎么写
  • 基于html5的网站开发wordpress主题工具
  • php网站开发的成功经历公司网站现状
  • 软件发布网站源码中国企业公示信息网
  • flash 的网站网站型销售怎么做
  • 营销型网站单页网站的域名和密码
  • 建网站保定seo自动发布外链工具
  • 做公众号关注网站做课件用这15大网站
  • 怎么制作公司自己网站店铺设计软件手机版
  • 深圳网站关键词优化公司哪家好怎么选择锦州网站建设