zhaofeihao / no-cross-no-crown

Nothing so bad, as not to be good for something.
MIT License
5 stars 1 forks source link

[笔记] - 前端重要基础知识点回顾 #30

Open zhaofeihao opened 4 years ago

zhaofeihao commented 4 years ago

[TOC] from boya

一、JS

1. 类的创建和继承

1.1 类的创建:

(1)工厂模式:在函数中定义该对象,并定义该对象的属性,为避免重复创建方法,可以将方法定义到函数外;

function createPerson(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    }
    return o;
}
解决的问题 存在的问题
传统Object构造函数或对象字面量创建对象的方式,会产生大量重复代码 没有解决对象识别的问题(即怎样知道一个对象的类型)

(2)构造函数:无需函数内部重新创建对象,而是用this代替,也无需return,同样也要将方法定义到函数外;

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    }
}

var person1 = new Person('feihao', '27', 'FE');
解决的问题 存在的问题
可以将构造函数的实例标识为一种特定的类型,如 person1 instanceof Person //true 1.每个方法都要在每个实例上重新创建一遍。2.如果将函数定义在构造函数外部解决问题1的话,新问题又来了,在全局作用域中定义的函数只能被某个对象调用,失去封装性。

要创建Person的新实例,必须使用 new 操作符,4个步骤:

// new 实现
function create(){
    // 创建一个新对象
    let obj = new Object();// 也可不使用new关键字
    // 获得构造函数
    let Con = [].shift.call(arguments);
    // 链接到原型
    obj.__proto__ = Con.prototype;
    // 绑定this,执行构造函数
    let result = Con.apply(obj,arguments);
    // 确保new出来的是个对象
    return typeof result === 'object' ? result : obj;
}

要检测对象类型,instanceof操作符更可靠一点,可以看一下instanceof是怎么实现的:

// instanceof 实现
function instanceof(left, right) {
    // 获得类型的原型
    let prototype = right.prototype
    // 获得对象的原型
    left = left.__proto__
    // 判断对象的类型是否等于类型的原型
    while (true) {
        if (left === null)
            return false
        if (prototype === left)
            return true
        left = left.__proto__
    }
}

(3)原型模式:函数中不对对象进行定义,而是用prototype定义属性;

function Person() {}

Person.prototype.name = 'feihao';
Person.prototype.age = '27';
Person.prototype.job = 'FE';
Person.prototype.sayName = function(){
    alert(this.name);
}

var person1 = new Person();
person1.sayName(); // feihao

var person2 = new Person();
person2.sayName(); // feihao

person1.sayName === person2.sayName; // true
解决的问题 存在的问题
可以让所有对象实例共享它所包含的属性和方法 无需传递初始化参数,原型中所有属性都是被实例共享的,引用类型会相互影响

(4)构造函数+原型模式的组合模式:函数中定义对象的方法之外的属性,原型定义对象的方法;

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ['tom', 'jerry']
}

Person.prototype = {
    constructor: Person,
    sayName: function() {
        alert(this.name);
    }
}
解决的问题 存在的问题
每个实例都会有自己的一份实例属性的副本,同时又共享方法的引用,最大限度节省内存;还支持向构造函数传参 使用广泛,认同度较高,暂无问题
1.2 类的继承:

(1)原型链继承

function SuperClass(){
    this.name = "women";
    this.bra = ["a","b"];
}
function SubClass(){
    this.subname = "your sister";
}
SubClass.prototype = new SuperClass();

var sub1 = new SubClass();
sub1.name = "man";
sub1.bra.push("c");
console.log(sub1.name);//man
console.log(sub1.bra);//["a","b","c"]

var sub2 = new SubClass();
console.log(sub1.name);//woman
console.log(sub2.bra);//["a","b","c"]

使用原型链实现继承时有一些需要我们注意的地方:

优点 缺点
简单易实现;实例是子类的的实例,也是父类的实例;父类增加属性,子类都能访问到。 为子类添加属性不能在构造器中,只能放在new父类之后;无法实现多继承;创造子类实例时,无法向父类构造传参;来自原型对象的属性是所有子类共享的。

(2)构造函数继承(经典继承)

function SuperClass() {
    this.name = "women";
    this.bra = ["a", "b"];
}
function SubClass() {
    this.subname = "your sister";
    //将SuperClass的作用域赋予当前构造函数,实现继承
    SuperClass.call(this);
}

var sub1 = new SubClass();
sub1.bra.push("c");
console.log(sub1.bra);//["a","b","c"]

var sub2 = new SubClass();
console.log(sub2.bra);//["a","b"]
优点 缺点
解决了引用类型的共享问题 如果在构造函数中有方法的定义,那么对于每一个实例都存在一份单独的Function引用,我们的目的其实是想共用这个方法,而且我们在超类型原型中定义的方法,在子类型实例中是无法调用到的

(3)实例继承

function Child(arg){
    return new Parent(arg);
};
优点 缺点
不限制调用方式,不管是new Child()还是Child(),返回的对象具有相同的效果 实例是父类的实例,不是子类的实例;

(4)拷贝继承

function child(arg){
    var son = new parent();
    for(var p in parent){
        child.prototype[p] = parent[p]
    }
};
new child();
优点 缺点
实现了多继承 效率低,内存占用高;无法继承父类不可枚举属性。

(5)组和继承

function SuperClass() {
    this.name = "women";
    this.bra = ["a", "b"];
}
SuperClass.prototype.sayWhat = function(){
    console.log("hello");
}
function SubClass() {
    this.subname = "your sister";
    SuperClass.call(this);             //第二次调用SuperClass
}
SubClass.prototype = new SuperClass(); //第一次调用SuperClass
var sub1 = new SubClass();
console.log(sub1.sayWhat());//hello
优点 缺点
函数可复用;可传参;实例既是子类实例又是父类实例;不存在引用属性共享问题;可以继承原型属性,也可以继承实例属性; 调用了两次超类型的构造函数

2. 原型链

JavaScript深入之从原型到原型链 0eda1a47a5b7c2c7c9e52ccc217854bb.png

加入Function: 1f6ec3e6a3fffa7cd8b6e2073a5bd844.png

3. 深浅拷贝

3.1 赋值与浅拷贝

赋值和浅拷贝的区别,看下面这个例子:

var obj1 = {
    'name' : 'zhangsan',
    'age' :  '18',
    'language' : [1,[2,3],[4,5]],
};

var obj2 = obj1;

var obj3 = shallowCopy(obj1);
function shallowCopy(src) {
    var target = {};
    for (var prop in src) {
        if (src.hasOwnProperty(prop)) {
            target[prop] = src[prop];
        }
    }
    return target;
}

obj2.name = "lisi";
obj3.age = "20";

obj2.language[1] = ["二","三"];
obj3.language[2] = ["四","五"];

console.log(obj1);  
//obj1 = {
//    'name' : 'lisi',
//    'age' :  '18',
//    'language' : [1,["二","三"],["四","五"]],
//};

console.log(obj2);
//obj2 = {
//    'name' : 'lisi',
//    'age' :  '18',
//    'language' : [1,["二","三"],["四","五"]],
//};

console.log(obj3);
//obj3 = {
//    'name' : 'zhangsan',
//    'age' :  '20',
//    'language' : [1,["二","三"],["四","五"]],
//};
obj1 obj2 obj3
原始对象 赋值操作得到的对象 浅拷贝得到的对象

结论: 改变 obj2name 属性和 obj3age 属性,可以看到,改变赋值得到的对象 obj2 同时也会改变原始值 obj1,而改变浅拷贝得到的的 obj3 则不会改变原始对象 obj1。这就可以说明赋值得到的对象 obj2 只是将指针改变,其引用的仍然是同一个对象,而浅拷贝得到的的 obj3 则是重新创建了新对象。

此外,还可以通过 Object.assign()Array.prototype.slice()Array.prototype.concat() 返回一个数组或者对象的浅拷贝。

3.2 深拷贝
function deepClone(source){
   if(!source || typeof source !== 'object'){
     throw new Error('error arguments', 'shallowClone');
   }
   var targetObj = source.constructor === Array ? [] : {};
   for(var keys in source){
      if(source.hasOwnProperty(keys)){
         if(source[keys] && typeof source[keys] === 'object'){
           targetObj[keys] = deepClone(source[keys]);
         }else{
           targetObj[keys] = source[keys];
         }
      } 
   }
   return targetObj;
}

// test example
var o1 = {
  arr: [1, 2, 3],
  obj: {
    key: 'value'
  },
  func: function(){
    return 1;
  }
};

deepClone(o1);

4. Ajax 用法

var XHR;
if(window.XMLHttpRequest){
    XHR = new XMLHttpRequest();
}else{
    XHR = new ActiveXobject("Microsoft.XMLHTTP");
};

XHR.open(GET/POST, url, true(异步)/false(同步));
XHR.send(string);
XHR.onreadystatechange = function(){
    if(XHR.readyState === 4 && XHR.status === 200){
    // readyState
    // 0: 请求未初始化;
    // 1: 服务器连接已建立;
    // 2: 请求已接收;
    // 3: 请求处理中;
    // 4: 请求已完成,且响应已就绪;
    // status 200: "OK";404: 未找到页面
        ele.innerHTML = XHR.responseText;//responseXML时需要解析一下
    };
};

ajax的优缺点:

优点 缺点
<1>.无刷新更新数据。AJAX最大优点就是能在不刷新整个页面的前提下与服务器通信维护数据。这使得Web应用程序更为迅捷地响应用户交互,并避免了在网络上发送那些没有改变的信息,减少用户等待时间,带来非常好的用户体验。 <1>.AJAX干掉了Back和History功能,即对浏览器机制的破坏。
<2>.异步与服务器通信。AJAX使用异步方式与服务器通信,不需要打断用户的操作,具有更加迅速的响应能力。优化了Browser和Server之间的沟通,减少不必要的数据传输、时间及降低网络上数据流量。 <2>.AJAX的安全问题。
<3>.前端和后端负载平衡。AJAX可以把以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。并且减轻服务器的负担,AJAX的原则是“按需取数据”,可以最大程度的减少冗余请求和响应对服务器造成的负担,提升站点性能。 <3>.对搜索引擎支持较弱。
<4>.基于标准被广泛支持。AJAX基于标准化的并被广泛支持的技术,不需要下载浏览器插件或者小程序,但需要客户允许JavaScript在浏览器上执行。随着Ajax的成熟,一些简化Ajax使用方法的程序库也相继问世。同样,也出现了另一种辅助程序设计的技术,为那些不支持JavaScript的用户提供替代功能。 <4>.破坏程序的异常处理机制。
<5>.界面与应用分离。Ajax使WEB中的界面与应用分离(也可以说是数据与呈现分离),有利于分工合作、减少非技术人员对页面的修改造成的WEB应用程序错误、提高效率、也更加适用于现在的发布系统。 <5>.违背URL和资源定位的初衷。
<6>.AJAX不能很好支持移动设备。
<7>.客户端过肥,太多客户端代码造成开发上的成本。

5. Javascript是一种弱类型语言,有什么优缺点?

优点:

缺点:

6. 前端模块化发展

6.1 函数封装:在文件中定义函数,用到时直接加载所在文件调用相应的函数。
function fn1(){
    function fn2(){
        statement;
    };
};

优缺点:污染了全局变量,无法保证不与其他函数模块之间发生冲突,而且各个变量之间没有直接的关系。

6.2 对象封装:在文件中定义相应的方法,挂载在对象上面,输出相应的对象。
var myModule = {
    val1:1,
    val2:2,
    fn1:function(){
        statement;
    },
    fn2:function(){
        statement;
    },
};

优缺点:解决了变量污染的问题,但是可以直接在变量外部修改相应的对象值,带来严重的安全隐患。

6.3 立即执行函数
var myModule = (function(){
    var val1 = 1;
    var val2 = 2;

    function fn1(){
        statement;
    };
    function fn2(){
        statement;
    };
    return {
        fn1:fn1,
        fn2:fn2,
    };
})();

优缺点:解决了对象值暴露的问题,是现代模块化规范的基础。

6.4 CommonJS

模块化规范是由服务器端的JavaScript应用带来的,CommonJS规范是由NodeJS发扬光大。

1、定义模块:根据CommonJS规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为global对象的属性 2、输出模块:模块只有一个出口,module.exports对象,我们需要把模块希望输出的内容放入该对象 3、加载模块:加载模块使用require方法,该方法读取一个文件并执行,返回文件内部的module.exports对象 4、同步读取模块,适合nodejs,由于网络的原因并不适合浏览器端(异步加载)

// 模块定义 myModel.js
var name = 'Byron';

function printName(){
    console.log(name);
}

function printFullName(firstName){
    console.log(firstName + name);
}

module.exports = {
    printName: printName,
    printFullName: printFullName
}
// 加载模块
var nameModule = require('./myModel.js');

nameModule.printName();

CommonJS的解决方案在服务端的实现很容易,但是仔细看上面的代码,会发现require是同步的。模块系统需要同步读取模块文件内容,并编译执行以得到模块接口。 产生的问题:脚本标签天生异步,传统CommonJS模块在浏览器环境中无法正常加载。

一种解决思路是,用一套标准模板来封装模块定义,但是对于模块应该怎么定义和怎么加载,又产生两种分歧:

6.5 AMD

AMD(Asynchronous Module Definition),中文名是异步模块定义的意思。它是一个在浏览器端模块化开发的规范。

其原理是异步加载模块,模块的加载不影响其后面语句的运行。所有依赖这个模块的语句都会添加进一个回调函数中,等到模块加载完成,回调函数就会执行。

由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎RequireJS,实际上AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出。

requireJS主要解决两个问题

  1. 多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
  2. js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长

一个使用requireJS的例子:

// 定义模块 myModule.js
define(['dependency'], function(){
    var name = 'Byron';
    function printName(){
        console.log(name);
    }

    return {
        printName: printName
    };
});
// 加载模块
require(['myModule'], function (my){
  my.printName();
});

requireJS定义了一个函数 define,它是全局变量,用来定义模块. define(id?, dependencies?, factory);

  1. id:可选参数,用来定义模块的标识,如果没有提供该参数,脚本文件名(去掉拓展名)
  2. dependencies:是一个当前模块依赖的模块名称数组
  3. factory:工厂方法,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值 在页面上使用require函数加载模块

require([dependencies], function(name){});

  1. [dependencies]:是一个数组,表示所依赖的模块
  2. function:是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块

require()函数在加载依赖的函数的时候是异步加载的,这样浏览器不会失去响应,它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。

6.6 CMD

CMD(Common Module Definition)通用模块定义,CMD规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同

Sea.js 推崇一个模块一个文件,遵循统一的写法。 define(id?, deps?, factory)

CMD推崇:

  1. 一个文件一个模块,所以经常就用文件名作为模块id
  2. CMD推崇依赖就近,所以一般不在define的参数中写依赖,在factory中写

factory是一个函数,有三个参数,function(require, exports, module)

  1. require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口:require(id)
  2. exports 是一个对象,用来向外提供模块接口
  3. module 是一个对象,上面存储了与当前模块相关联的一些属性和方法
// 定义模块  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
});
// 加载模块
seajs.use(['myModule.js'], function(my){
    // ...
});
6.7 AMD与CMD区别

最明显的区别就是在模块定义时对依赖的处理不同:

  1. AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块
  2. CMD推崇就近依赖,只有在用到某个模块的时候再去require

这种区别各有优劣,只是语法上的差距,而且requireJS和SeaJS都支持对方的写法

AMD和CMD最大的区别是对依赖模块的执行时机处理不同,注意不是加载的时机(都是异步加载)或者方式不同。

AMD依赖前置,js可以方便知道依赖模块是谁,立即加载,而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略。

同样都是异步加载模块,AMD在加载模块完成后就会执行该模块,所有模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行。

CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。

这也是很多人说AMD用户体验好,因为没有延迟,依赖模块提前执行了,CMD性能好,因为只有用户需要的时候才执行的原因

6.8 ES6模块(服务器&浏览器端通用)

7. "use strict"的好处和坏处分别是什么?

好处:

坏处:

8. Promise/async&await

9. 变量提升&函数声明和函数表达式

10. 事件循环机制、调用栈以及任务队列(event loop、call stack、task queue)

11. 图片懒加载

<img src="" data-src="http://ww4.sinaimg.cn/large/006y8mN6gw1fa5obmqrmvj305k05k3yh.jpg" alt="">

将图片src设为空,将图片url放到自定义属性上(如 data-src) 监听页面scroll,遍历图片并判断,符合条件的图片将自定义属性上的url赋值给src

function lazyload() { //监听页面滚动事件
    var seeHeight = document.documentElement.clientHeight; //可见区域高度
    var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
    var img = document.querySelectorAll('img');
    var n = 0;
    for (var i = n; i < num; i++) {
        if (img[i].offsetTop < seeHeight + scrollTop) {
        if (img[i].getAttribute("src") == "") {
            img[i].src = img[i].getAttribute("data-src");
        }
        n = i + 1;
    }
    }
}

12. 拖动

<html>
    <ul id='drag'>
      <li draggable="true">1</li>
      <li draggable="true">2</li>
      <li draggable="true">3</li>
    </ul>
</html>
<script>
  var ele;
  document.querySelector('#drag').addEventListener('dragstart', function (e) {
    ele = e.target;
    ele.classList.add('draging');
  })
  document.querySelector('#drag').addEventListener('dragover', function (e) {
    e.preventDefault();

    if (e.target.nodeName === 'LI') {
      e.target.parentNode.insertBefore(ele, e.target);
    }
  })
  document.querySelector('#drag').addEventListener('drop', function (e) {
    ele.classList.remove('draging');
  })
 </sctipt>

13.使用框架带来的好处

14. call、apply、bind 的 polyfill

二、HTML/CSS

1. @import和link的用法与区别

2. CSS性能优化

  1. 慎重使用高性能属性:浮动、定位;

  2. 尽量减少页面重排、重绘
    触发重排的操作主要是几何因素: 1).页面第一次渲染 在页面发生首次渲染的时候,所有组件都要进行首次布局,这是开销最大的一次重排。 2).浏览器窗口尺寸改变 3).元素位置和尺寸发生改变的时候 4).新增和删除可见元素 5).内容发生改变(文字数量或图片大小等等) 6).元素字体大小变化。 7).激活CSS伪类(例如::hover)。 8).设置style属性 9).查询某些属性或调用某些方法。比如说: offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 除此之外,当我们调用getComputedStyle方法,或者IE里的currentStyle时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。 触发重绘的操作主要有: 1).vidibility、outline、背景色等属性的改变

    优化重排:

    • 将多次改变样式属性的操作合并成一次操作,减少DOM访问。 如果要批量添加DOM,可以先让元素脱离文档流,操作完后再带入文档流,这样只会触发一次重排。(fragment元素的应用)
    • 将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。
    • 由于display属性为none的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发两次重排。
    • 在内存中多次操作节点,完成后再添加到文档中去。例如要异步获取表格数据,渲染到页面。可以先取得数据后在内存中构建整个表格的html片段,再一次性添加到文档中去,而不是循环添加每一行。
  3. 去除空规则:{};

  4. 属性值为0时,不加单位;

  5. 属性值为浮动小数0.xx,可以省略小数点之前的0;

  6. 标准化各种浏览器前缀:带浏览器前缀的在前。标准属性在后;

  7. 不使用@import前缀,它会影响css的加载速度,并且可能出现FOUC(无样式内容闪烁(html先于css加载出来));

  8. 充分利用css继承属性,减少代码量;

  9. 抽象提取公共样式,减少代码量;

  10. 选择器优化嵌套,尽量避免层级过深;

  11. css雪碧图,同一页面相近部分的小图标,方便使用,减少页面的请求次数,但是同时图片本身会变大,使用时,优劣考虑清楚,再使用;

  12. 将css文件放在页面最上面

3. 清除浮动的方法

3.1 clear: both;

第一种运用clear的方式在需要清浮动的元素下放一个div,并加上clear:both

.clear{
    clear:both;
}

第二种方式是用伪元素:before或:after

.content:after{
   content:'';
   display:'block';
   clear:both;
}
3.2 overflow:hidden

该方法利用了BFC的特性,当元素有以下特性时,触发BFC

规则(BFC)

4. flex

flex-container:

flex-item:

5. CSS水平/垂直居中

各种页面常见布局

5.1. 水平居中
  1. 标签
  2. text-align : center;
  3. margin:0 auto;
  4. display: flex; justify-content:center;
  5. position:absolute;left:50%;transform:translateX(-50%);
5.2 垂直居中
  1. 单行文本 height = line-height
  2. table-cell
  3. position
  4. flex
  5. transform

6. 两列布局

  1. 左侧absolute并且定宽,右侧margin-left不小于左侧宽度
  2. 左侧float left,右侧margin-left不小于左侧宽度
  3. Flexbox:左侧设定宽度,右侧flex设为1,表示占据全部剩余空间
  4. 利用BFC不与浮动元素重叠的特性:左侧float left,左右两侧设置不同的background的颜色
  5. 三列布局与两列布局类似,右侧同样绝对定位,但要加top:0;

7. 0.5px边框

.border-bottom{
    position: relative;
    border-top: none !important;
}

.border-bottom::after {
    content: " ";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 1px;
    background-color: #e4e4e4;
    -webkit-transform-origin: left bottom;
    transform-origin: left bottom;
}
/* 2倍屏 */
 @media only screen and (-webkit-min-device-pixel-ratio: 2.0) {
    .border-bottom::after {
        -webkit-transform: scaleY(0.5);
        transform: scaleY(0.5);
      }
 }

/* 3倍屏 */
     @media only screen and (-webkit-min-device-pixel-ratio: 3.0) {
    .border-bottom::after {
        -webkit-transform: scaleY(0.33);
        transform: scaleY(0.33);
      }
 }

8. 视图模型

a57aab3daf2de27d5dd6a730c4dfef32.gif

9. 页面DOM节点太多,会出现什么问题?如何优化?

问题 页面卡顿,帧率下降

优化:

三、网络

1. tcp/ip三次握手

关闭TCP连接:

2. 一个页面从输入URL到页面加载完成的过程中发生了什么?

3. 前端跨域的解决方法

<body>
    <p id='jsonp'>JSONP:</p>
    <script>
        let jsonpDOM = document.querySelector('#jsonp');

        // JSONP 访问测试
        function addScriptTag(src) {
            let script = document.createElement('script');
            script.setAttribute('type', 'text/javascript');
            script.src = src;
            document.body.appendChild(script);
        };

        // 可指定任意请求触发
        window.onload = function() {
            addScriptTag('http://127.0.0.1:3000/jsonp?callback=test');
            // addScriptTag('http://127.0.0.1:3000/users?callback=test');
        };

        function test(data) {
            jsonpDOM.innerHTML += JSON.stringify(data.content);
        };
    </script>
</body>

后端:

let server = function (req, res) {
    res.setHeader('Access-Control-Allow-Origin', 'http://piao.qunar.com');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With');
    res.setHeader('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
    res.setHeader('Set-Cookie', 'name=lei.li');
    let urlObj = url.parse(req.url,true);

    if (urlObj.pathname === '/' || urlObj.pathname === '/index.html') {
        fs.readFile('./index.html', (err, data) => {
            if (err) {
                res.writeHead(500);
                res.end('server error');
            } else {
                res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
                res.end(data);
            }
        })
    } else if (req.method === 'GET' && urlObj.pathname === '/users') {
        // 判断其他接口
        res.writeHead(200, {
            'Content-Type': 'application/json'
        });

        if(urlObj.query.callback) {
            res.end(urlObj.query.callback+'('+JSON.stringify(data)+')');
        } else {
            res.end(JSON.stringify(data));
        }
    } else if (urlObj.pathname === '/jsonp') {
        res.writeHead(200,{'Content-Type':'application/json;charset=utf-8'});
         let data = {
             "content": "这是一条通过 jsonp 方式获取的数据"
         };
         data = JSON.stringify(data);
         //假设这里定义的回调函数名为test
         var callback = 'test'+'('+data+');';
         res.end(callback);
    } else {
        res.writeHead(404, { 'Content-Type': 'application/json' });
        res.end('404 Not Found');
    }
}

优点:不像XHR对象那样调用AJAX时受到同源策略限制;兼容性很好;请求完毕后可以通过调用callback的方式回传结果,方便调用。 缺点:只支持GET;只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题;要确定jsonp请求是否失败并不容易。

2) 创建a.com/proxy.html,并加入如下代码

<head>
  <script>
  function proxy(url, func){
    var isFirst = true,
        ifr = document.createElement('iframe'),
        loadFunc = function(){
          if(isFirst){
            ifr.contentWindow.location = 'http://a.com/cs1.html';
            isFirst = false;
          }else{
            func(ifr.contentWindow.name);
            ifr.contentWindow.close();
            document.body.removeChild(ifr);
            ifr.src = '';
            ifr = null;
          }
        };

    ifr.src = url;
    ifr.style.display = 'none';
    if(ifr.attachEvent) 
        ifr.attachEvent('onload', loadFunc);
    else 
        ifr.onload = loadFunc;

    document.body.appendChild(iframe);
  }
</script>
</head>
<body>
  <script>
    proxy('http://www.baidu.com/', function(data){
      console.log(data);
    });
  </script>
</body>

3) 在b.com/cs1.html中包含:

<script>
    window.name = '要传送的内容';
</script>

4. HTTP 缓存

5. 常见HTTP状态码

6. 减少页面加载时间的方法(加载时间指感知的时间或者实际加载时间)

  1. 减少http请求(合并文件、合并图片)
  2. 压缩合并javascript、css
  3. 图片格式的选择
  4. 服务端用gzip压缩文件
  5. 在网址最后加上/
  6. 标明图片的宽高

7. 实时网络传输方式及区别

AJAX Long-Polling: 客户端使用普通的http方式向服务器端请求网页 客户端执行网页中的JavaScript脚本,向服务器发送数据、请求信息 服务器并不是立即就对客户端的请求作出响应,而是等待有效的更新 当信息是有效的更新时,服务器才会把数据推送给客户端 当客户端接收到服务器的通知时,立即会发送一个新的请求,进入到下一次的轮询

HTML5 Server Sent Events (SSE) / EventSource: 客户端使用普通的http方式向服务器端请求网页 客户端执行网页中的JavaScript脚本,与服务器之间建立了一个连接 当服务器端有有效的更新时,会发送一个事件到客户端 服务器到客户端数据的实时推送,大多数内容是你需要的 你需要一台可以做Event Loop的服务器 不允许跨域的连接

HTML5 Websockets: 客户端使用普通的http方式向服务器端请求网页 客户端执行网页中的JavaScript脚本,与服务器之间建立了一个连接 服务器和客户端之间,可以双向的发送有效数据到对方 服务器可以实时的发送数据到客户端,同时客户端也可以实时的发送数据到服务器 你需要一台可以做Event Loop的服务器 使用 WebSockets 允许跨域的建立连接 它同样支持第三方的websocket主机服务器,例如Pusher或者其它。这样你只需要关心客户端的实现 ,降低了开发难度。

WebRTC: WebRTC是一种点对点类型的传输方式,它支持多种传输协议,如:UDP、TCP甚至是抽象层的协议。设计它时同时考虑到了允许使用可靠和不可靠的两种方式传输数据。这种技术一般应用在传输数据量较大的内容,比如音、视频等流媒体的传输。

Comet: Comet是一种用于web的推送技术,能使服务器实时地将更新的信息传送到客户端,而无须客户端发出请求,目前有两种实现方式,长轮询和iframe流。 Event Loop Event Loop是一个程序结构,用于等待和发送消息和事件。 长轮询 长轮询是在打开一条连接以后保持,等待服务器推送来数据再关闭的方式。 iframe流 iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长链接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的JavaScript),来实时更新页面。iframe流方式的优点是浏览器兼容好,Google公司在一些产品中使用了iframe流,如Google Talk。

8. 为什么利用多个域名来提供网站资源会更有效

  1. CDN缓存更方便
  2. 突破浏览器并发限制(一般每个域名建立的链接不超过6个)
  3. Cookieless,节省带宽,尤其是上行带宽一般比下行要慢
  4. 对于UGC(用户生产内容)的内容和主站隔离,防止不必要的安全问题(上传js窃取主站cookie之类的)。正是这个原因要求用户内容的域名必须不是自己主站的子域名,而是一个完全独立的第三方域名。
  5. 数据做了划分,甚至切到了不同的物理集群,通过子域名来分流比较省事。这个可能被用的不多。

PS:关于Cookie的问题,带宽是次要的,安全隔离才是主要的。关于多域名,也不是越多越好,虽然服务器端可以做泛解释,浏览器做dns解释也是耗时间的,而且太多域名,如果要走https的话,还有要多买证书和部署的问题。

9. CDN

优点:

  1. 访问加速
  2. 减轻源站负载
  3. 抗住攻击

10. HTTP methods

HTTP请求的方法: HTTP/1.1协议中共定义了八种方法(有时也叫“动作”),来表明Request-URL指定的资源不同的操作方式

  1. OPTIONS 返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性
  2. HEAD 向服务器索与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以再不必传输整个响应内容的情况下,就可以获取包含在响应小消息头中的元信息。
  3. GET 向特定的资源发出请求。注意:GET方法不应当被用于产生“副作用”的操作中,例如在Web Application中,其中一个原因是GET可能会被网络蜘蛛等随意访问。Loadrunner中对应get请求函数:web_link和web_url
  4. POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 Loadrunner中对应POST请求函数:web_submit_data,web_submit_form
  5. PUT 向指定资源位置上传其最新内容
  6. DELETE 请求服务器删除Request-URL所标识的资源
  7. TRACE 回显服务器收到的请求,主要用于测试或诊断
  8. CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 注意: 1)方法名称是区分大小写的,当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Mothod Not Allowed);当服务器不认识或者不支持对应的请求方法时,应返回状态码501(Not Implemented)。 2)HTTP服务器至少应该实现GET和HEAD/POST方法,其他方法都是可选的,此外除上述方法,特定的HTTP服务器支持扩展自定义的方法。

11. 项目中用到的优化方法

12. 页面的渲染过程

13. DNS

OSI体系结构分为7层:应用层、表示层、会话层、传输层、网路层、链路层、物理层。

DNS协议在应用层。他的作用就是将域名解析成对应的IP地址。

DNS在进行区域传输的时候使用TCP协议,其它时候则使用UDP协议; DNS的规范规定了2种类型的DNS服务器,一个叫主DNS服务器,一个叫辅助DNS服务器。在一个区中主DNS服务器从自己本机的数据文件中读取该区的DNS数据信息,而辅助DNS服务器则从区的主DNS服务器中读取该区的DNS数据信息。当一个辅助DNS服务器启动时,它需要与主DNS服务器通信,并加载数据信息,这就叫做区传送(zone transfer)。

为什么既使用TCP又使用UDP? 首先了解一下TCP与UDP传送字节的长度限制: UDP报文的最大长度为512字节,而TCP则允许报文长度超过512字节。当DNS查询超过512字节时,时则使用TCP发送。通常传统的UDP报文一般不会大于512字节。

区域传送时使用TCP,主要有一下两点考虑: 1.辅域名服务器会定时(一般时3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,则会执行一次区域传送,进行数据同步。区域传送将使用TCP而不是UDP,因为数据同步传送的数据量比一个请求和应答的数据量要多得多。 2.TCP是一种可靠的连接,保证了数据的准确性。

域名解析时使用UDP协议: 客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过TCP三次握手,这样DNS服务器负载更低,响应更快。虽然从理论上说,客户端也可以指定向DNS服务器查询的时候使用TCP,但事实上,很多DNS服务器进行配置的时候,仅支持UDP查询包。

DNS使用的是TCP协议还是UDP协议

五、算法

1.最大公约数与最小公倍数

辗转相除法:

function getGcd(a, b) {
    let max = Math.max(a, b);
    let min = Math.min(a, b);
    if (max % min === 0) {
        return min;
    } else {
        return getGcd(max % min, min);
    }
}

function getLcm(a, b) {
    return a * b / getGcd(a, b);
}