阅读量:2
51. Web- garden和web-farm有什么不同?
参考回答:
web-garden和 web-farm都是网络托管系统。唯一的区别是 web-garden是在单个服务器中包含许多处理器的设置,而web-farm是使用多个服务器的较大设置
52. JavaScript是怎么样实现继承的?
参考回答:
方法1:借助构造函数实现继承(部分继承) /** * 借助构造函数实现继承 */ function Parent1() { this.name = 'parent'; } Parent1.prototype.say = function() {}; // 不会被继承 function Child1() { // 继承:子类的构造函数里执行父级构造函数 // 也可以用apply // parent的属性都会挂载到child实例上去 // 借助构造函数实现继承的缺点:①如果parent1除了构造函数里的内容,还有自己原型链上的东西, 自己原型链上的东西不会被child1继承 // 任何一个函数都有prototype属性,但当它是构造函数的时候,才能起到作用(构造函数是有自己的 原型链的) Parent1.call(this); this.type = 'child1'; } console.log(new Child1); (1)如果父类的属性都在构造函数内,就会被子类继承。 (2)如果父类的原型对象上有方法,子类不会被继承。 方法2:借助原型链实现继承 /** * 借助原型链实现继承 */ function Parent2() { this.name = 'name'; this.play = [1, 2, 3] } function Child2() { this.type = 'child2'; } Child2.prototype = new Parent2(); // prototype使这个构造函数的实例能访问到原型对象上 console.log(new Child2().__proto__); console.log(new Child2().__proto__ === Child2.prototype); // true var s1 = new Child2(); // 实例 var s2 = new Child2(); console.log(s1.play, s2.play); s1.play.push(4); console.log(s1.__proto__ === s2.__proto__); // true // 父类的原型对象 (1)原型链的基本原理:构造函数的实例能访问到它的原型对象上 (2)缺点:原型链中的原型对象,是共用的 方法3:组合方式 /** ①组合方式优化1: ②组合方式优化2(最优解决方案): * 组合方式 */ function Parent3() { this.name = 'name'; this.play = [1, 2, 3]; } function Child3() { Parent3.call(this); this.type = 'child3'; } Child3.prototype = new Parent3(); var s3 = new Child3(); var s4 = new Child3(); s3.play.push(4); console.log(s3.play, s4.play); // 父类的构造函数执行了2次 // 构造函数体会自动执行,子类继承父类的构造函数体的属性和方法 ①组合方式优化1: /** * 组合继承的优化方式1:父类只执行了一次 */ function Parent4() { this.name = 'name'; this.play = [1, 2, 3]; } function Child4() { Parent4.call(this); this.type = 'child4'; } Child4.prototype = Parent4.prototype; // 继承父类的原型对象 var s5 = new Child4(); var s6 = new Child4(); console.log(s5 instanceof Child4, s5 instanceof Parent4); // true console.log(s5.constructor); // Parent4 //prototype里有个constructor属性,子类和 父 类的原型对象就是同一个对象, s5的constructor就是父类的constructor ②组合方式优化2(最优解决方案): /** * 组合继承优化2 */ function Parent5() { this.name = 'name'; this.play = [1, 2, 3]; } function Child5() { Parent5.call(this); this.type = 'child5'; } Child5.prototype = Object.create(Parent5.prototype); // Object.create创建的对象 就 是参数 Child5.prototype.constructor = Child5; var s7 = new Child5(); console.log(s7 instanceof Child5, s7 instanceof Parent5); console.log(s7.constructor); // 构造函数指向Child5 优缺点: 原型链继承的缺点 1、字面量重写原型 一是字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。 2、借用构造函数(类式继承) 借用构造函数虽然解决了刚才两种问题,但没有原型,则复用无从谈起。所以我们需要原型链+借用构 造函数的模式,这种模式称为组合继承 3、组合式继承 组合式继承是比较常用的一种继承方法,其背后的思路是 使用原型链实现对原型属性和方法的继承,而 通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证 每个实例都有它自己的属性。
53. 简述JavaScript如何通过new构建对象?
参考回答:
通过new操作符构建对象的步骤如下。 (1)创建一个新的对象,这个对象的类型是 object. (2)将this变量指向该对象。 (3)将对象的原型指向该构造函数的原型。 (4)执行构造函数,通过this对象,为实例化对象添加自身属性方法。 (5)将this引用的新创建的对象返回。 代码如下。 function demo(Base) { var obj ={}; //this =obj obj. __proto__= Base. prototype; School.call(obj) return obj }
54. 简述JavaScript中继承的实现方法?
参考回答:
方法1:借助构造函数实现继承(部分继承) /** * 借助构造函数实现继承 */ function Parent1() { this.name = 'parent'; } Parent1.prototype.say = function() {}; // 不会被继承 function Child1() { // 继承:子类的构造函数里执行父级构造函数 // 也可以用apply // parent的属性都会挂载到child实例上去 // 借助构造函数实现继承的缺点:①如果parent1除了构造函数里的内容,还有自己原型链上的东西, 自己原型链上的东西不会被child1继承 // 任何一个函数都有prototype属性,但当它是构造函数的时候,才能起到作用(构造函数是有自己的 原型链的) Parent1.call(this); this.type = 'child1'; } console.log(new Child1); (1)如果父类的属性都在构造函数内,就会被子类继承。 (2)如果父类的原型对象上有方法,子类不会被继承。 方法2:借助原型链实现继承 /** * 借助原型链实现继承 */ function Parent2() { this.name = 'name'; this.play = [1, 2, 3] } function Child2() { this.type = 'child2'; } Child2.prototype = new Parent2(); // prototype使这个构造函数的实例能访问到原型对象上 console.log(new Child2().__proto__); console.log(new Child2().__proto__ === Child2.prototype); // true var s1 = new Child2(); // 实例 var s2 = new Child2(); console.log(s1.play, s2.play); s1.play.push(4); console.log(s1.__proto__ === s2.__proto__); // true // 父类的原型对象 (1)原型链的基本原理:构造函数的实例能访问到它的原型对象上 (2)缺点:原型链中的原型对象,是共用的 方法3:组合方式 /** ①组合方式优化1: ②组合方式优化2(最优解决方案): * 组合方式 */ function Parent3() { this.name = 'name'; this.play = [1, 2, 3]; } function Child3() { Parent3.call(this); this.type = 'child3'; } Child3.prototype = new Parent3(); var s3 = new Child3(); var s4 = new Child3(); s3.play.push(4); console.log(s3.play, s4.play); // 父类的构造函数执行了2次 // 构造函数体会自动执行,子类继承父类的构造函数体的属性和方法 ①组合方式优化1: /** * 组合继承的优化方式1:父类只执行了一次 */ function Parent4() { this.name = 'name'; this.play = [1, 2, 3]; } function Child4() { Parent4.call(this); this.type = 'child4'; } Child4.prototype = Parent4.prototype; // 继承父类的原型对象 var s5 = new Child4(); var s6 = new Child4(); console.log(s5 instanceof Child4, s5 instanceof Parent4); // true console.log(s5.constructor); // Parent4 //prototype里有个constructor属性,子类和 父 类的原型对象就是同一个对象, s5的constructor就是父类的constructor ②组合方式优化2(最优解决方案): /** * 组合继承优化2 */ function Parent5() { this.name = 'name'; this.play = [1, 2, 3]; } function Child5() { Parent5.call(this); this.type = 'child5'; } Child5.prototype = Object.create(Parent5.prototype); // Object.create创建的对象 就 是参数 Child5.prototype.constructor = Child5; var s7 = new Child5(); console.log(s7 instanceof Child5, s7 instanceof Parent5); console.log(s7.constructor); // 构造函数指向Child5 优缺点: 原型链继承的缺点 1、字面量重写原型 一是字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。 2、借用构造函数(类式继承) 借用构造函数虽然解决了刚才两种问题,但没有原型,则复用无从谈起。所以我们需要原型链+借用构 造函数的模式,这种模式称为组合继承 3、组合式继承 组合式继承是比较常用的一种继承方法,其背后的思路是 使用原型链实现对原型属性和方法的继承,而 通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证 每个实例都有它自己的属性。
55. 简述JavaScript构造函数的特点 ?
参考回答:
构造函数的函数名首字母大写,构造函数类似于一个模板,可以使用new关键字执行构造函数,创建实例化对象
56. 简述列出 JavaScript常用继承方式并说明其优缺点 ?
参考回答:
常用继承方式及其优缺点如下。 (1)构造函数式继承是指在子类的作用域上,执行父类的构造函数,并传递参数构造函数式继承虽然解决了对父类构造函数的复用问题,但没有更改原型。 (2)类(原型链)式继承是指将父类的实例化对象添加给子类的原型。执行构造函数是没有意义的,因为我们只想继承原型链上的属性和方法,当执行父类的构造函数时,没有添加参数,所以执行构造函数的结果是不正确的。父类构造函数中的数据,没有直接添加在子类的实例化对象上,而是添加在原型上。子类型实例化时无法复用父类的构造函数。 (3)组合式继承是比较常用的一种继承方法,其背后的思路是,使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承。这样,既在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性。但其问题是导致父类的构造函数执行了两次:一次是在构造函数式继承中执行的;另一次是在类式继承中执行的。 使用上述继承要注意以下几点 (1)在构造函数式继承中,属性的赋值一定在继承的后面执行,否则会产生覆盖问题。 (2)在类式继承中,原型上属性或者方法的赋值一定在继承后面,否则会产生覆盖问题。 (3)在类式继承中,在原型上添加属性或者方法一定使用点语法的形式,不可以给对象赋值。 (4)在类式继承中,要为原型添加属性方法对象,可以在继承后面通过库的 extend方法(或 ECMAScript6提供了 assign方法)实现。
57. 用 JavaScript写一个实现寄生式继承的方法 ?
参考回答:
以下代码实现了寄生式继承方法 inherit。 var inherit =(function() { //定义寄生类 function F() { } ; return function(sub, sup) { //寄生类的原型指向父类 E.prototype= sup.prototype; //继承父类的原型 sub.prototype=new F (); //更正构造函数 sub.prototype.constructor =sub; //返回子类 return sub; } } )() //父类 function Star(names) { this.names = names } Star.prototype.getNames = function() { return this.names //子类 function Moviestar(names, age) { //构造函数式继承 Star.apply(this, arguments) this.age = age; //寄生式继承 inherit(Moviestar, star); MovieStar.prototype.getAge =function () { return this.age; } console. log(new Moviestar( 'xiao bai', 20))
58. 简述说出你熟知的 Javascript继承方式 ?
参考回答:
有以下几种继承方式 (1)构造函数式继承。 (2)类(原型链)式继承。 (3)组合式继承(混合使用构造函数式和类式)。 (4)寄生式继承。 (5)继承组合式继承(混合使用构造函数式和寄生式)。 (6)原子继承。 (7)多继承。 (8)静态继承。 (9)特性继承。 (10)构造函数拓展式继承。 (11)工厂式继承
59. 面向对象的特性有哪些?
参考回答:
(1)抽象,就是忽略一个主题中与当前目标无关的那些方面,以便更充分地关注与当前目标相关的方面。 (2)封装,利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据存放在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口,使之与外部发生联系。 (3)继承,使用已存在的类的定义作为基础,建立新类的技术。新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。 (4)多态,程序中定义的引用变量所指向的具体类型和通过该引用变量触发的方法调用在编程时并不确定,而在程序运行期间才能确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量触发的方法调用到底是哪个类中实现的方法,必须在程序运行期间才能决定。
60. 简述面向对象编程的三大特点 ?
参考回答:
(1)封装,即将描述同一个对象的属性和方法定义在一个对象中。 (2)继承,即父对象中的属性和方法被子对象使用。 (3)多态,即同一个对象在不同情况下呈现不同的形态(注意,在 JavaScript中无多态”的概念)。多态有以下两种形式。 重载,即同一方法名,根据传入的参数不同,而执行不同操作。 重写,即子对象在继承父对象的属性或方法后,重新定义一个新的属性或方法以覆盖从父对象中继承的属性或方法。
61. 简述方法重载( Overload)与方法重写( Override)的区别?
参考回答:
方法重载属于编译时的多态,根据传递的参数不同,执行不同的业务逻辑,得到不同的结果。方法重写属于运行时的多态,子类原型指向父类原型,子类重写父类的方法,在调用子类方法的时候使用子类的方法,从而重写父类中定义的方法
62. JavaScript如何判断某个对象是否包含指定成员?
参考回答:
通过以下方式判断。 (1)使用 obj. hasOwnProperty(" ")。 如果找到,返回true;否则,返回 false。 (2)使用“属性名”in对象如果找到,返回true;否则,返回 false。 (3)直接使用ob属性名作为判断的条件,如下所示。 if (obj. demo === undefined) 若不包含,条件值为true;若包含,条件值为 false
63. 简述JavaScript this通常指向 ?
参考回答:
在运行时,this关键字指向正在调用该方法的对象 1、作为构造函数执行 function Foo(name) { this.name = name; } var f = new Foo('zhangsan') 2、作为对象属性执行 var obj = { name: 'A', printName: function() { console.log(this.name); } } obj.printName(); 3、作为普通函数执行 function fn() { console.log(this); } fn(); 4、call apply bind function fn1(name, age) { alert(name); console.log(this); } fn1.call({x: 100}, 'zhangsan', 20); fn1.apply({x:100}, ['zhangsan', 20]) var fn2 = function (name, age) { // 必须是函数表达式,不能是函数声明,即不能是function fn2(name, age) {} alert(name); console.log(this); }.bind({y:200}); fn2('zhangsan', 20);
64. JavaScript如何判断属性是自有属性还是原型属性?
参考回答:
方法如下 (1)要判断自有属性,使用 obj. hasOwn Property(“属性名”) (2)要判断原型属性,使用“属性名” in obj&&!obj.hasOwn Property(“属性名”)的形式。
65. JavaScript 实现对象的继承有哪几种方式?
参考回答:
(1)修改对象的 _proto_,如下所示 Object.set Prototypeof(子对象,父对象) (2)修改构造函数的原型对象,如下所示。 构造函数.prototype=对象 (3)使用原子继承方法 Object.create(父对象[, { 属性列表 } ]),如下所示。 var demo = Object.create(obj)
66. 简述什么是函数柯里化?
参考回答:
柯里化,是函数式编程的一个重要概念。它既能减少代码冗余,也能增加可读性。另外,附带着还能用 来装逼。 先给出柯里化的定义:在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列 使用一个参数的函数的技术。 柯里化的定义,理解起来有点费劲。为了更好地理解,先看下面这个例子: function sum (a, b, c) { console.log(a + b + c); } sum(1, 2, 3); // 6 毫无疑问,sum 是个简单的累加函数,接受3个参数,输出累加的结果。 假设有这样的需求,sum的前2个参数保持不变,最后一个参数可以随意。那么就会想到,在函数内,是 否可以把前2个参数的相加过程,给抽离出来,因为参数都是相同的,没必要每次都做运算。 如果先不管函数内的具体实现,调用的写法可以是这样: sum(1, 2)(3); 或这样 sum(1, 2)(10); 。 就是,先把前2个参数的运算结果拿到后,再与第3个参数相加。 这其实就是函数柯里化的简单应用。
67. 简述浏览器是如何渲染页面的 ?
参考回答:
解析 HTML 文件,创建 DOM 树 自上而下,遇到任何样式(link、style)与脚本(script)都会阻塞(外部样式不阻塞后续外部脚本的加载)。 解析 CSS 优先级:浏览器默认设置<用户设置<外部样式<内联样式 构建渲染树 将 CSS 与 DOM 合并,构建渲染树(Render Tree) 布局和绘制 布局和绘制,重绘(repaint)和重排(reflow)
68. 简述meta作用和解释 ?
参考回答:
核心 提供给页面的一些元信息(名称 / 值对),有助于 SEO。 属性值 name 名称 / 值对中的名称。author、description、keywords、generator、revised、others。 把content 属性关联到一个名称。 http-equiv 没有 name 时,会采用这个属性的值。content-type、expires、refresh、set-cookie。把content 属性关联到 http 头部 content 名称 / 值对中的值, 可以是任何有效的字符串。 始终要和 name 属性或 http-equiv 属性一起使用scheme用于指定要用来翻译属性值的方案。
69. 简述PWA使用过吗?serviceWorker的使用原理是啥? ?
参考回答:
渐进式网络应用(PWA) 是谷歌在2015年底提出的概念。基本上算是web应用程序,但在外观和感觉上与 原生app 类似。支持 PWA 的网站可以提供脱机工作、推送通知和设备硬件访问等功能。Service Worker 是浏览器在后台独立于网页运行的脚本,它打开了通向不需要网页或用户交互的功能的大门。 现在,它们已包括如推送通知和后台同步等功能。 将来, Service Worker 将会支持如定期同步或地理围栏等其他功能。 本教程讨论的核心功能是拦截和处理网络请求,包括通过程序来管理缓存中的响应。
70. 如果一个构造函数,bind了一个对象,用这个构造函数创建出的实例会继承这 个对象的属性吗?为什么 ?
参考回答:
不会继承,因为根据 this 绑定四大规则,new 绑定的优先级高于 bind 显示绑定,通过 new 进行构造函数调用时,会创建一个新对象,这个新对象会代替 bind 的对象绑定,作为此函数的 this,并且在此函数没有返回对象的情况下,返回这个新建的对象
71. 简述箭头函数和普通函数有啥区别?箭头函数能当构造函数吗? ?
参考回答:
普通函数通过 function 关键字定义, this 无法结合词法作用域使用,在运行时绑定,只取决于函数的调用方式,在哪里被调用,调用位置。(取决于调用者,和是否独立运行) 箭头函数使用被称为 “胖箭头” 的操作 => 定义,箭头函数不应用普通函数 this 绑定的四种规则,而是根据外层(函数或全局)的作用域来决定 this,且箭头函数的绑定无法被修改(new 也不行)。 箭头函数常用于回调函数中,包括事件处理器或定时器箭头函数和 var self = this,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域 没有原型、没有 this、没有 super,没有 arguments,没有 new.target不能通过 new 关键字调用一个函数内部有两个方法:[[Call]] 和 [[Construct]],在通过 new 进行函数调用时,会执行[[construct]] 方法,创建一个实例对象,然后再执行这个函数体,将函数的 this 绑定在这个 实例对象上 当直接调用时,执行 [[Call]] 方法,直接执行函体箭头函数没有 [[Construct]] 方法,不能被用作构造函数调用,当使用 new 进行函数调用时会 报错。 function foo() { return (a) => { console.log(this.a); } } var obj1 = { a: 2 } var obj2 = { a: 3 } var bar = foo.call(obj1); bar.call(obj2);
72. 简述深度优先遍历和广度优先遍历,如何实现?
参考回答:
深度优先遍历——是指从某个顶点出发,首先访问这个顶点,然后找出刚访问 这个结点的第一个未被访问的邻结点,然后再以此邻结点为顶点,继续找它的 下一个顶点进行访问。重复此步骤,直至所有结点都被访问完为止。 广度优先遍历——是从某个顶点出发,首先访问这个顶点,然后找出刚访问这 个结点所有未被访问的邻结点,访问完后再访问这些结点中第一个邻结点的所 有结点,重复此方法,直到所有结点都被访问完为止。 //1.深度优先遍历的递归写法 function deepTraversal(node) { let nodes = [] if (node != null) { nodes.push[node] let childrens = node.children for (let i = 0; i < childrens.length; i++) deepTraversal(childrens[i]) } return nodes} //2.深度优先遍历的非递归写法 function deepTraversal(node) { let nodes = [] if (node != null) { let stack = [] //同来存放将来要访问的节点 stack.push(node) while (stack.length != 0) { let item = stack.pop() //正在访问的节点 nodes.push(item) let childrens = item.children for ( let i = childrens.length - 1; i >= 0; i-- //将现在访问点的节点的子节点存入 stack,供将来访问 ) stack.push(childrens[i]) } } return nodes} //3.广度优先遍历的递归写法 function wideTraversal(node) { let nodes = [], i = 0 if (node != null) { nodes.push(node) wideTraversal(node.nextElementSibling) node = nodes[i++] wideTraversal(node.firstElementChild) } return nodes}//4.广度优先遍历的非递归写法 function wideTraversal(node) { let nodes = [], i = 0 while (node != null) { nodes.push(node) node = nodes[i++] let childrens = node.children for (let i = 0; i < childrens.length; i++) { nodes.push(childrens[i]) } } return nodes }
73. 简述下 npm 模块安装机制,为什么输入 npm install 就可以自动安装对应的模块 ?
参考回答:
npm 模块安装机制: 发出 npm install 命令 1 查询 node_modules 目录之中是否已经存在指定模 块 若存在,不再重新安装 若不存在 npm 向 registry 查询模块压缩包的网址 下载压缩包,存放在根目录下的.npm 目录里 解压压缩包到当前项目的 node_modules 目录
74. 简述重绘和回流(Repaint & Reflow),以及如何进行优化 ?
参考回答:
1. 浏览器渲染机制 浏览器采用流式布局模型(Flow Based Layout) 浏览器会把 HTML 解析成 DOM,把 CSS 解析成 CSSOM,DOM 和 CSSOM 合并就产生了渲染树(Render Tree)。 有了 RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。 由于浏览器使用流式布局,对 Render Tree 的计算通常只需要遍历一次就可以完成,但 table 及其内部元素除外,他们可能需要多次计算,通常要花 3 倍于同等元素的时间,这也是为什么要避免使用 table 布局的原因之一。 2. 重绘 由于节点的几何属性发生改变或者由于样式发生改变而不会影响布局的,称为重绘,例如 outline, visibility, color、background-color 等,重绘的代价是高昂的,因为浏览器必须验证 DOM 树上其他节点元素的可见性。 3. 回流 回流是布局或者几何属性需要改变就称为回流。回流是影响浏览器性能的关键因素,因为其变化涉及到部分页面(或是整个页面)的布局更新。一个元素的回流可能会导致了其所有子元素以及 DOM 中紧随其后的节点、祖先节点元素的随后的回流。 我的组件 错误:错误的描述… 错误纠正 第一步 第二步 在上面的 HTML 片段中,对该段落( 标签)回流将会引发强烈的回流,因为它是一个子节点。这也导致了祖先的回流(div.error 和 body – 视浏览器而定)。 此外, 和 也会有简单的回流,因为其在 DOM 中在回流元素之后。大部分的回流将导致页面的重新渲染。 回流必定会发生重绘,重绘不一定会引发回流。 4. 浏览器优化 现代浏览器大多都是通过队列机制来批量更新布局,浏览器会把修改操作放在队列中,至少一个浏览器刷新(即 16.6ms)才会清空队列,但当你获取布局信息的时候,队列中可能有会影响这些属性或方法返回值的操作,即使没有,浏览器也会强制清空队列,触发回流与重绘来确保返回正确的值。 主要包括以下属性或方法: 1、offsetTop、offsetLeft、offsetWidth、offsetHeight 2、scrollTop、scrollLeft、scrollWidth、scrollHeight 3、clientTop、clientLeft、clientWidth、clientHeight 4、width、height 5、getComputedStyle() 6、getBoundingClientRect() 所以,我们应该避免频繁的使用上述的属性,他们都会强制渲染刷新队列。 5. 减少重绘与回流 CSS 1、使用 transform 替代 top 2、使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局 3、避免使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。 4、尽可能在 DOM 树的最末端改变 class,回流是不可避免的,但可以减少其影 响。尽可能在 DOM 树的最末端改变 class,可以限制了回流的范围,使其影响尽可能少的节点。 5、避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。 对于第一种设置样式的方式来说,浏览器只需要找到页面中所有的 span 标签然后设置颜色,但是对于第二种设置样式的方式来说,浏览器首先需要找到所有的 span 标签,然后找到 span 标签上的 a 标签,最后再去找到 div 标签,然后给符合这种条件的 span 标签设置颜色,这样的递归过程就很复杂。所以我们应该尽可能的避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽量少的添加无意义标签,保证层级扁平。 将动画效果应用到 position 属性为 absolute 或 fixed 的元素上,避免影响其他元素的布局,这样只是一个重绘,而不是回流,同时,控制动画速度可以选择 requestAnimationFrame,详见探讨 requestAnimationFrame。 避免使用 CSS 表达式,可能会引发回流。 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点,例如 will-change、video、iframe 等标签,浏览器会自动将该节点变为图层。 CSS3 硬件加速(GPU 加速),使用 css3 硬件加速,可以让 transform、opacity、filters 这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color 这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。 JavaScript 1、避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class并一次性更改 class 属性。 2、避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有 DOM操作,最后再把它添加到文档中。 3、避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。 4、对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流
75. 简述cookie 和 token 都存放在 header 中,为什么不会劫持 token ?
参考回答:
1、攻击者通过 xss 拿到用户的 cookie 然后就可以伪造 cookie 了。 2、或者通过 csrf 在同个浏览器下面通过浏览器会自动带上 cookie 的特性在通过 用户网站-攻击者网站-攻击者请求用户网站的方式 浏览器会自动带上 cookie 但是 token 1、不会被浏览器带上 问题 2 解决 2、token 是放在 jwt 里面下发给客户端的 而且不一定存储在哪里 不能通过document.cookie 直接拿到,通过 jwt+ip 的方式 可以防止 被劫持 即使被劫持也是无效的 jwt
76. 简述Virtual DOM 真的比操作原生 DOM 快吗 ?
参考回答:
1. 原生 DOM 操作 vs. 通过框架封装操作。 这是一个性能 vs. 可维护性的取舍。框架的意义在于为你掩盖底层的 DOM 操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。针对任何一个 benchmark,我都可以写出比任何框架更快的手动优化,但是那有什么意义呢?在构建一个实际应用的时候,你难道为每一个地方都去做手动优化吗?出于可维护性的考虑,这显然不可能。框架给你的保证是,你在不需要手动优化的情况下,我依然可以给你提供过得去的性能。 2. 对 React 的 Virtual DOM 的误解。 React 从来没有说过 “React 比原生操作 DOM 快”。React 的基本思维模式是每次有变动就整个重新渲染整个应用。如果没有 Virtual DOM,简单来想就是直接重置 innerHTML。很多人都没有意识到,在一个大型列表所有数据都变了的情况下,重置 innerHTML 其实是一个还算合理的操作... 真正的问题是在“全部重新渲染” 的思维模式下,即使只有一行数据变了,它也需要重置整个innerHTML,这时候显然就有大量的浪费。 我们可以比较一下 innerHTML vs. Virtual DOM 的重绘性能消耗: innerHTML: render html string O(template size) + 重新创建所有 DOM 元 素 O(DOM size) Virtual DOM: render Virtual DOM + diff O(template size) + 必要的 DOM更新 O(DOM change)Virtual DOM render + diff 显然比渲染 html 字符串要慢,但是!它依然是纯 js层面的计算,比起后面的 DOM 操作来说,依然便宜了太多。可以看到,innerHTML 的总计算量不管是 js 计算还是 DOM 操作都是和整个界面的大小相关,但 Virtual DOM 的计算量里面,只有 js 计算和界面大小相关,DOM 操 作是和数据的变动量相关的。前面说了,和 DOM 操作比起来,js 计算是极其便宜的。这才是为什么要有 Virtual DOM:它保证了 1)不管你的数据变化多少,每次重绘的性能都可以接受;2) 你依然可以用类似 innerHTML 的思路去写你的应用。 3. MVVM vs. Virtual DOM 相比起 React,其他 MVVM 系框架比如 Angular, Knockout 以及 Vue、Avalon采用的都是数据绑定:通过 Directive/Binding 对象,观察数据变化并保留对实际 DOM 元素的引用,当有数据变化时进行对应的操作。MVVM 的变化检查是数据层面的,而 React 的检查是 DOM 结构层面的。MVVM 的性能也根据变动检测的实现原理有所不同:Angular 的脏检查使得任何变动都有固定的 O(watcher count) 的代价;Knockout/Vue/Avalon 都采用了依赖收集,在 js 和 DOM 层面都是 O(change): 脏检查:scope digest O(watcher count) + 必要 DOM 更新 O(DOMchange) 依赖收集:重新收集依赖 O(data change) + 必要 DOM 更新 O(DOMchange)可以看到,Angular 最不效率的地方在于任何小变动都有的和 watcher 数量相关的性能代价。但是!当所有数据都变了的时候,Angular 其实并不吃亏。依赖收集在初始化和数据变化的时候都需要重新收集依赖,这个代价在小量更新的时候几乎可以忽略,但在数据量庞大的时候也会产生一定的消耗。MVVM 渲染列表的时候,由于每一行都有自己的数据作用域,所以通常都是每一行有一个对应的 ViewModel 实例,或者是一个稍微轻量一些的利用原型继承的 "scope" 对象,但也有一定的代价。所以,MVVM 列表渲染的初始化几乎一定比 React 慢,因为创建 ViewModel / scope 实例比起 Virtual DOM 来说要昂贵很多。 这里所有 MVVM 实现的一个共同问题就是在列表渲染的数据源变动时,尤其是当数据是全新的对象时,如何有效地复用已经创建的 ViewModel 实例和DOM 元素。 假如没有任何复用方面的优化,由于数据是 “全新” 的,MVVM 实际上需要销毁之前的所有实例,重新创建所有实例,最后再进行一次渲染!这就是为什么题目里链接的 angular/knockout 实现都相对比较慢。相比之下,React 的变动检查由于是 DOM 结构层面的,即使是全新的数据,只要最后渲染结果没变,那么就不需要做无用功。 Angular 和 Vue 都提供了列表重绘的优化机制,也就是 “提示” 框架如何有效地复用实例和 DOM 元素。比如数据库里的同一个对象,在两次前端 API 调用里面会成为不同的对象,但是它们依然有一样的 uid。这时候你就可以提示track by uid 来让 Angular 知道,这两个对象其实是同一份数据。那么原来这份数据对应的实例和 DOM 元素都可以复用,只需要更新变动了的部分。或者,你也可以直接 track by $index 来进行 “原地复用”:直接根据在数组里的位置进行复用。在题目给出的例子里,如果 angular 实现加上 track by $index 的话,后续重绘是不会比 React 慢多少的。甚至在 dbmonster 测试中,Angular 和Vue 用了 track by $index 以后都比 React 快: dbmon (注意 Angular 默认版本 无优化,优化过的在下面) 顺道说一句,React 渲染列表的时候也需要提供 key 这个特殊 prop,本质上和 track-by 是一回事。 4. 性能比较也要看场合 在比较性能的时候,要分清楚初始渲染、小量数据更新、大量数据更新这些不同的场合。Virtual DOM、脏检查 MVVM、数据收集 MVVM 在不同场合各有不同的表现和不同的优化需求。Virtual DOM 为了提升小量数据更新时的性能,也需要针对性的优化,比如 houldComponentUpdate 或是 immutable data。 初始渲染:Virtual DOM > 脏检查 >= 依赖收集 小量数据更新:依赖收集 >> Virtual DOM + 优化 > 脏检查(无法优化) >Virtual DOM 无优化 大量数据更新:脏检查 + 优化 >= 依赖收集 + 优化 > Virtual DOM(无法/无需优化)>> MVVM 无优化 不要天真地以为 Virtual DOM 就是快,diff 不是免费的,batching 么 MVVM 也能做,而且最终 patch 的时候还不是要用原生 API。在我看来 Virtual DOM 真正的价值从来都不是性能,而是它 1) 为函数式的 UI 编程方式打开了大门; 2)可以渲染到 DOM 以外的 backend,比如 ReactNative。 5. 总结 以上这些比较,更多的是对于框架开发研究者提供一些参考。主流的框架 + 合理的优化,足以应对绝大部分应用的性能需求。如果是对性能有极致需求的特殊情况,其实应该牺牲一些可维护性采取手动优化:比如 Atom 编辑器在文件渲染的实现上放弃了 React 而采用了自己实现的 tile-based rendering;又比如在移动端需要 DOM-pooling 的虚拟滚动,不需要考虑顺序变化,可以绕过框架的内置实现自己搞一个
77. 简述什么是webkit ?
参考回答:
Webkit 是浏览器引擎,包括 html 渲染和 js 解析功能,手机浏览器的主流内核,与之相对应的引擎有 Gecko(Mozilla Firefox 等使用)和 Trident(也称 MSHTML,IE 使用)。 对于浏览器的调试工具要熟练使用,主要是页面结构分析,后台请求信息查看,js 调试工具使用,熟练使用这些工具可以快速提高解决问题的效率
78. 简述前端 templating(Mustache, underscore, handlebars)的作用, 怎么用 ?
参考回答:
Web 模板引擎是为了使用户界面与业务数据(内容)分离而产生的,Mustache 是一个 logic-less (轻逻辑)模板解析引擎,它的优势在于可以应用在 Javascript、PHP、Python、Perl 等多种编程语言中。 Underscore 封装了常用的 JavaScript 对象操作方法,用于提高开发效率。 Handlebars 是 JavaScript 一个语义模板库,通过对 view 和 data 的分离来快速构建Web 模板
79. 简述你知道多少种Doctype文档类型 ?
参考回答:
该标签可声明三种 DTD 类型,分别表示严格版本、过渡版本以及基于框架的 HTML 文档。 HTML 4.01 规定了三种文档类型:Strict、Transitional 以及 Frameset。 XHTML 1.0 规定了三种 XML 文档类型:Strict、Transitional 以及 Frameset。 Standards (标准)模式(也就是严格呈现模式)用于呈现遵循最新标准的网页,而 Quirks(包容)模式(也就是松散呈现模式或者兼容模式)用于呈现为传统浏览器而设计的网页
80. 简述什么是同源限制 ?
参考回答:
我们举例说明:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真 实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户 名,密码就轻松到手了。 缺点: 现在网站的JS 都会进行压缩,一些文件用了严格模式,而另一些没有。这时这些本来是严格模式的文 件,被 merge 后,这个串就到了文件的中间,不仅没有指示严格模式,反而在压缩后浪费了字节
81. 简述对网站重构的理解?
参考回答:
网站重构:在不改变外部行为的前提下,简化结构、添加可读性,而在网站前端保持一致的行为。也就是说是在不改变UI的情况下,对网站进行优化, 在扩展的同时保持一致的UI。 对于传统的网站来说重构通常是: @ 表格(table)布局改为DIV+CSS @ 使网站前端兼容于现代浏览器(针对于不合规范的CSS、如对IE6有效的) @ 对于移动平台的优化 @ 针对于SEO进行优化 @ 深层次的网站重构应该考虑的方面 @ 减少代码间的耦合 @ 让代码保持弹性 @ 严格按规范编写代码 @ 设计可扩展的API @ 代替旧有的框架、语言(如VB) @ 增强用户体验 @ 通常来说对于速度的优化也包含在重构中 @ JS、CSS、image等前端资源(通常是由服务器来解决) @ 程序的性能优化(如数据读写) @ 采用CDN来加速资源加载 @ 对于JS DOM的优化 @ HTTP服务器的文件缓存
82. 简述什么是设计模式?
参考回答:
设计模式(英语 design pattern)是对面向对象设计中反复出现的问题的解决方案。这个术语是在1990年代由Erich Gamma等人从建筑设计领域引入到计算机科学中来的。这个术语的含义还存有争议。算法不是设计模式,因为算法致力于解决问题而非设计问题。设计模式通常描述了一组相互紧密作用的类与对象。设计模式提供一种讨论软件设计的公共语言,使得熟练设计者的设计经验可以被初学者和其他设计者掌握。设计模式还为软件重构提供了目标。 随着软件开发社群对设计模式的兴趣日益增长,已经出版了一些相关的专著,定期召开相应的研讨会,而且Ward Cunningham为此发明了WikiWiki用来交流设计模式的经验。 总之,设计模式就是为了解决某类重复出现的问题而出现的一套成功或有效的解决方案
83. 常见的设计模式有哪些?
参考回答:
GOF提出的23种设计模式,分为三大类。 (1)创建型模式,共5种,分别是工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 (2)结构型模式,共7种,分别是适配器模式、装饰器模式、代理模式、外观模式桥接模式、组合模式、享元模式。 (3)行为型模式,共11种,分别是策略模式、模板方法模式、观察者模式、选代子模式、責任链模式、命令模式、备忘录模弌、状态模式、访问者模式、中介者模式、解释器模式。 在前端开发中,有些特定的模式不太适用。当然,有些适用于前端的模式并未包含在这23种设计模式中,如委托模式、节流模式等
84. 简述工厂模式的概念 ?
参考回答:
其概念如下: 工厂模式需要3个基本步骤,原料投入、加工过程以及成品出厂,例如以下代码。 function playerFactory (username){ var user= new object (); user .username = username; return user ; } var xm = playerFactory( 'xiao ming ') player Factory函数中传递的参数就是“基本原料的投入”。从 var user= new Object()直到return之前,都属于“加工过程”。最后的 return就如同“成品出厂”
85. 简述工厂模式的缺陷 ?
参考回答:
缺陷如下 (1)没有使用new关键字,在创建对象的过程中,看不到构造函数实例化的过程。 (2)每个实例化的对象都创建相应的变量和函数,因此需要更多的空间进行属性和方法的存储,从而降低了性能,造成资源的浪费。
订阅本专栏,查看更多前端面试题目