前端面试题总结

71 minute read

总结一下面试题,考研考不上了吧55555555555,开始准备前端春招了,争取到了头条白金码,加油吧

Html及浏览器

HTML新增标签

form;input的search、tel、email、url、number、color、file;article;nav;header;section;nav;aside…

http的格式以及各部分的主要组成

  • 起始行

请求报文的起始行:该行包含了一个方法和一个请求的URL,还包含HTTP 的版本。

响应报文的起始行:该行包含了响应报文使用的HTTP版本、数字状态码、原因短语。

  • 头部

包含通用头部(Connection、Date、Cache-Control)、请求头部(If-None-Match、Authorization、Cookie)、响应头部(Set-Cookie、server)、实体头部(Allow(允许请求的方法)、Content-Type、ETag、Expires、Last-Modified)

ContentType类型:

  • application/x-www-form-urlencoded:提交post请求时会使用这种方式。浏览器提交原生form表单,不设置enctype时,会默认是这样的方式。
  • multipart/form-data、application/json:同样是请求时使用的,指定即可。
  • text/html

移动端适配

<meta name="viewport" content="width=device-width, initial-scale=1.0">

移动端点透

移动端在touch上一共有四个事件,touchstart -> touchmove -> touchend -> touchcancel;此外,按钮都有一个onclick事件。通过代码实验可以知道,当仅仅点击时,会触发touchstart、touchend和click事件,而当在屏幕上移动时,则会触发touchstart、touchmove、touchend,不会触发click事件。当一个用户在点击屏幕的时候,系统会触发touch事件和click事件,touch事件优先处理,touch事件经过 捕获,处理, 冒泡 一系列流程处理完成后, 才回去触发click事件。

from jianshu.com

<!DOCTYPE html>
<html>
  <head>
    <style type="text/css">
      #level0 {
        /* width: 500px;
        height: 500px; */
        position: relative;
      }

      #level1-0 {
        position: absolute;
        z-index: 1;
        background: red;
        width: 500px;
        height: 500px;
      }

      #level1-1 {
        background: green;
        width: 500px;
        height: 500px;
      }
    </style>
  </head>
  <body>
    <div id="level0">
      <div id="level1-0">
      </div>
      <div id="level1-1">
      </div> 
    </div>
  </body>
  <script type="text/javascript">

    var level10 = document.getElementById("level1-0");
    var level11 = document.getElementById("level1-1");


    level10.addEventListener('touchstart', function(e) {
      level10.style.display = 'none';
    });

    level11.onclick = function() {
      console.log('level11莫名被点击了');
    }

  </script>
</html>

如上代码中,运行后是level1-0绝对定位在level1-1上层。按理说点击 level1-0的时候,level1-0会阻挡所有的事件,事件不会传递给level1-1,当点击level1-0的时候,实际上level1-1也发生了点击事件,即上面的输出结果为

level1-0消失, 输出 level11莫名被点击了, 这就是点透

点透发生的条件,

A 和 B不是后代继承关系(如果是后代继承关系的话,就直接是冒泡子类的话题了) A发生touch, A touch后立即消失, B事件绑定click A z-index大于B,即A显示在B浮层之上

点透发生的理由: 当手指触摸到屏幕的时候,系统生成两个事件,一个是touch 一个是click,touch先执行,touch执行完成后,A从文档树上面消失了,而且由于移动端click还有延迟200-300ms的关系,当系统要触发click的时候,发现在用户点击的位置上面,目前离用户最近的元素是B,所以就直接把click事件作用在B元素上面了

解决方法,为level10的touchxxx时间添加一个e.preventDefault();,在touch事件里面,调用e.preventDefault() 就可以阻止本次点击系统触发的click事件,即本次相关的click都不会执行

level10.addEventListener('touchend', function(e) {
    e.preventDefault();
});
// 或延迟消失300s(因为移动端会有300ms延迟)
setTimeout(() => {
    level10.style.display = 'none';
}, 300);

从输入url到显示页面都发生了什么

  • 输入url,常见的http协议的端口号是80,https是443
  • 查看浏览器是否有缓存,其中分为强制缓存相对缓存

    强制缓存:判断HTTP首部字段:cache-control表示存储的文件在多长时间内均有效、Expires表示到达系统某个时间内数据均有效

    相对缓存:通过HTTP的last-modified服务器返回的字段,表示最后一次更新的时间,Etag是资源的实体标识(哈希字符串),当资源内容更新时,Etag会改变。服务器会判断Etag是否发生变化,如果变化则返回新资源,否则返回304。

  • dns查询,分为迭代查询和递归查询
  • TCP三次握手建立连接
  • 浏览器向服务器发送HTTP请求
  • 浏览器接收响应

    服务器在收到浏览器发送的HTTP请求之后,会将收到的HTTP报文封装成HTTP的Request对象,并通过不同的Web服务器进行处理,处理完的结果以HTTP的Response对象返回,主要包括状态码,响应头,响应报文三个部分。

    状态码主要包括以下部分

    1xx:指示信息–表示请求已接收,继续处理。

    2xx:成功–表示请求已被成功接收、理解、接受。

    3xx:重定向–要完成请求必须进行更进一步的操作。

    4xx:客户端错误–请求有语法错误或请求无法实现。

    5xx:服务器端错误–服务器未能实现合法的请求。

  • 页面渲染

    其中会涉及到reflow(回流,会导致重新渲染)和repaint(重绘,只会重画一部分)。

    减少reflow/repaint的措施:

    1. 不要一条一条地修改 DOM 的样式。与其这样,还不如预先定义好 css 的 class,然后修改 DOM 的 className。
    2. 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量。
    3. 为动画的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他们的 CSS 是不会 reflow 的。
    4. 千万不要使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。

DNS的过程

  • 用户主机上运行着DNS的客户端,就是我们的PC机或者手机客户端运行着DNS客户端了
  • 浏览器将接收到的url中抽取出域名字段,就是访问的主机名,比如http://www.baidu.com/, 并将这个主机名传送给DNS应用的客户端
  • DNS客户机端向DNS服务器端发送一份查询报文,报文中包含着要访问的主机名字段(中间包括一些列缓存查询以及分布式DNS集群的工作)
  • 该DNS客户机最终会收到一份回答报文,其中包含有该主机名对应的IP地址
  • 一旦该浏览器收到来自DNS的IP地址,就可以向该IP地址定位的HTTP服务器发起TCP连接

http和https区别

HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

xss和csrf

  • 跨站脚本xss(cross site script)

XSS是指恶意攻击者利用网站没有对用户提交数据进行转义处理或者过滤不足的缺点,进而添加一些代码,嵌入到web页面中去。使别的用户访问都会执行相应的嵌入代码。源于过于信任客户端提交的数据,对提交的数据进行过滤,去掉script等关键字

  • 跨站请求伪造csrf(cross-site request forgery)

CSRF是一种夹持用户在已经登陆的web应用程序上执行非本意的操作的攻击方式。相比于XSS,CSRF是利用了系统对页面浏览器的信任,XSS则利用了系统对用户的信任。

csrf

Token

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:

  • 客户端使用用户名跟密码请求登录
  • 服务端收到请求,去验证用户名与密码
  • 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
  • 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
  • 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
  • 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

session,cookie,sessionStorage,localStorage

  • cookie和session都是用来跟踪浏览器用户身份的会话方式。cookie保存在浏览器端,session保存在服务器端。cookie虽然保存在浏览器端,但Cookie是服务器发给客户端的特殊信息,cookie是以文本的方式保存在客户端,每次请求时都带上它。
  • session机制:当服务器收到请求需要创建session对象时,首先会检查客户端请求中是否包含sessionid。如果有sessionid,服务器将根据该id返回对应session对象。如果客户端请求中没有sessionid,服务器会创建新的session对象,并把sessionid在本次响应中返回给客户端。通常使用cookie方式存储sessionid到客户端,在交互中浏览器按照规则将sessionid发送给服务器。如果用户禁用cookie,则要使用URL重写,可以通过response.encodeURL(url) 进行实现;API对encodeURL的结束为,当浏览器支持Cookie时,url不做任何处理;当浏览器不支持Cookie的时候,将会重写URL将SessionID拼接到访问地址后。
  • 应用场景:
    1. cookie: 网上商城中的购物车、保存用户登录信息、将某些数据放入session中,供同一用户的不同页面使用、防止用户非法登录
    2. session:Session用于保存每个用户的专用信息,变量的值保存在服务器端,通过SessionID来区分不同的客户。主要用于:网上商城中的购物车、保存用户登录信息、将某些数据放入session中,供同一用户的不同页面使用、防止用户非法登录。
  • localStorage与sessionStorage:
    1. localStorage:localStorage的生命周期是永久的,关闭页面或浏览器之后localStorage中的数据也不会消失。localStorage除非主动删除数据,否则数据永远不会消失。常用于长期登录(+判断用户是否已登录),适合长期保存在本地的数据
    2. sessionStorage:的生命周期是在仅在当前会话下有效。sessionStorage引入了一个“浏览器窗口”的概念,sessionStorage是在同源的窗口中始终存在的数据。只要这个浏览器窗口没有关闭,即使刷新页面或者进入同源另一个页面,数据依然存在。但是sessionStorage在关闭了浏览器窗口后就会被销毁。同时独立的打开同一个窗口同一个页面,sessionStorage也是不一样的。sessionStorage:敏感账号一次性登录;
    3. 存储位置:localStorage和sessionStorage都保存在客户端,不与服务器进行交互通信。
    4. 获取方式:localStorage:window.localStorage;;sessionStorage:window.sessionStorage;。

css、js、dom的渲染关系

  • css不会阻碍dom的解析,但会阻碍dom渲染,很好理解。
  • js会阻碍dom解析。
  • 遇到js时,如果没有async或defer标签,会触发页面渲染。

defer和async都是在html解析(parase)的时候下载js文件,不同的是,async下载完后,会立即执行,而defer会等待html解析完成完后才执行。如果该脚本依赖于另一个脚本或被另一个脚本依赖,则使用defer。

JavaScript

js基本数据类型

Boolean、Undefined、Null、Number、String

js内建对象

Array, Boolean, Date, Error, Function, Math, Number, Object, RegExp, String

js获取变量类型的几种方法

  • typeof:可以判断基本类型,但无法判断对象具体类型;且当判断基本包装类型创建的实例如 new String()时会判断成Object
  • Object.prototype.toString.call():可以判断具体对象类型,可以把new Array()、new Reg()分别判断成数组和正则表达式
  • instanceof:变量 instanceof 对象类型,返回Boolean值,且如new Array() instanceof Array或Object都返回true。当instanceof undefined和null时会报错。

常见算法题

  • 把一串数字表示成千位分隔

/(?=(\B\d{3})+$)/g;;‘?=’表示前瞻,exp1(?=exp2)查找exp2前面的exp1(因此在这里就是查找三个数字前面的空白);\B是非单词边界,表示所匹配的这个空后面不能是一个单词边界

  • 大数相加
function sumStrings(a, b) {
    var res = '', c = 0;
    a = a.split('');
    b = b.split('');
    while (a.length || b.length || c) {
        c += ~~a.pop() + ~~b.pop(); // ~~将字符串转化为数字
        res = c % 10 + res;
        c = c > 9; // 如果大于9则有进位,c=1
    }
    return res.replace(/^0+/, '');
}
  • 合并两个有序数组
function combine(n1, n2){
    let i = n1.length - 1, j = n2.length - 1, tar = i + j + 1;
    while (j >= 0) {
        n1[tar--] = i >= 0 && n1[i] > n2[j] ? n1[i--] : n2[j--];
    }
    return n1;
}
  • 快速排序

[4,3,5,2,1]第一趟排序的结果为[2,3,1,4,5];;即先,5与1先交换,然后到2时,此时2与初始4交换。

function _quickSort(num, left, right) {
    if (left >= right) return num;
    var i = left, j = right, temp = num[left];
    while (i != j) {
        while (num[j] >= temp && i < j) j--;
        while (num[i] <= temp && i < j) i++;
        if(i < j){ // 选一大一小交换
            let t = num[i];
            num[i] = num[j];
            num[j] = t;
        }
    }
    num[left] = num[i]; // 已下将最终基数归位
    num[i] = temp;
    _quickSort(num, left, i - 1);
    _quickSort(num, i + 1, right);
}
  • 全排列
function permute(nums){
    var result = [];
    backtrack(nums, 0, result);
    return result;
}

function backtrack(nums, begin, result) {
    if(begin >= nums.length){
        return result.push(nums.slice());
    } 
    for(let i = begin; i < nums.length; i++){
        var temp;
        temp = nums[begin];
        nums[begin] = nums[i];
        nums[i] = temp;
        backtrack(nums, begin + 1, result);
        nums[i] = nums[begin];
        nums[begin] = temp;
    }
}

js深克隆

let cloneObj = function(obj){
    let str, result = obj.constructor === Array ? [] : 
        obj.constructor ? new obj.constructor : {};

    if (!obj || typeof obj !== 'Object') {
        return obj;
    }
    for (let key in obj) {
        result[key] = cloneObj(obj[key]);
    }
    return result;
}

setTimeOut和promise区别

promise的优先级比setTimeOut高,其中promise的then和setTimeout都是依次加入到消息队列后面去的。

setTimeout谈js运行机制

from cnblog.com

Js有一个运行队列如图

img

如图,setTimeOut和setInterval都是追加到运行队列中的

对于setTimeOut和setInterval的对比,setInterval可以通过setTimeout通过递归调用实现

function func() {
  setTimeout(function() {
    // some code
    func();
  }, 10);
}

但区别是,setInterval的回调是并列的,前一个回调(有没有执行)并不会影响后一个回调(插入队列),同时,如果等待队列里已经有同一个interval函数的回调了,将不会有相同的回调插入等待队列(有点像函数防抖);而setTimeout之间的回调是嵌套的,后一个回调是前一个回调的回调,所以造成setTimeOut的执行时间往往大于设定的时间,因为每次插入到后续队列,都需要等待代码执行完成。

首屏渲染优化

alloyteam.com

medium.com

白屏时间是指浏览器从响应用户输入网址地址,到浏览器开始显示内容的时间。影响因素有网络,服务端性能,前端页面结构设计。

首屏时间是指浏览器从响应用户输入网络地址,到首屏内容渲染完成的时间(渲染完成就是首屏渲染时间)。影响因素有白屏时间,资源下载执行时间。

以腾讯前端团队alloyteam之前发布的手Q首屏渲染为例, above-the-fold-01腾讯团队旧有的模式是按一个常规的顺序的加载的,即将css放在head标签内,为了避免阻塞将js放在底部。这样尽管可以避免js阻塞加载,但是往往在页面渲染完成之后,还需要等待js加载,如果前端HTMl页面有数据需要填充或者有节点需要渲染,则很容易造成页面reflow/repaint,也大大减慢了首屏加载时间。

above-the-fold-01

改进后为的模式为什么缩短渲染时间?

第一,首屏数据拉取逻辑置于顶部,是为了数据能够第一时间返回,相比起将数据拉取逻辑放在外部资源会少了一个JS资源加载的往返时间。

第二,首屏渲染css及js逻辑优先内联HTML,这是为了当HTML文档返回时CSS和JS能够立即执行。

第三,次屏逻辑延后处理和执行,各种数据上报最好是延时上报,这样可以减少阻塞。

js懒加载

JavaScript实现

let lazyImages = [...document.querySelectorAll('.lazy-image')]
let inAdvance = 300 // 自定义一个高度,当距离300px到达图片时加载

function lazyLoad() {
    lazyImages.forEach(image => {
        if (image.offsetTop < window.innerHeight + window.pageYOffset + inAdvance) { // 距离xxpx时加载图片
            image.src = image.dataset.src
            image.onload = () => image.classList.add('loaded')
        }
    })

    // if all loaded removeEventListener
}

lazyLoad()

window.addEventListener('scroll', _.throttle(lazyLoad, 16)) // 用到了lodash的节流函数
window.addEventListener('resize', _.throttle(lazyLoad, 16))

函数节流

是为了限制函数一段时间内只能执行一次,在延时的时间内,方法若被触发,则直接退出方法,这是与防抖的区别,防抖是在延时内触发则重新计时,也就是每次都只执行一段时间的最后一次

const _.throttle = (func, wait) => {
    let timer;
    return () => {
        if (timer) {
            return;
        }

        timer = setTimeout(() => {
            func();
            timer = null;
        }, wait);
    };
};

函数防抖

函数防抖在执行目标方法时,会等待一段时间。当又执行相同方法时,若前一个定时任务未执行完,则 clear 掉定时任务,重新定时。

const _.debounce = (func, wait) => {
    let timer;

    return () => {
        clearTimeout(timer);
        timer = setTimeout(func, wait);
    };
};

var和let的区别

通过var定义的变量,作用域是整个封闭函数,是全域的 。通过let定义的变量,作用域是在块级或是子块中。 且var会提升变量

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
//相当于
var foo;  //声明且初始化为undefined
console.log(foo);
foo=2;

js闭包

  • 什么是闭包

闭包是指有权访问另一个函数作用域中的变量的函数,函数可以通过作用域链连接起来,函数内部的变量可以保存在其他函数作用域内,所有的JavaScript函数都是闭包,

例子

function fun(n,o) {
  console.log(o);
  return {
    fun:function(m){
      return fun(m,n); //n是最外层的n
    }
  };
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,0,0,0;
// 因为其中fun.fun的n为最外层传进去的n,所以会一直不变
var b = fun(0).fun(1).fun(2).fun(3);//undefined,0,1,2
// 同理,由于不断的递归调用,所以最外层的n始终为上一次传进去的
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,0,1,1
// 闭包,即n在这个作用域内会一直保存

js构造函数和原型对象

总结自cnblog

构造函数所有的实例对象都可以继承构造器函数中的属性和方法。但是,同一个对象实例之间,无法共享属性

uml图

由图可以看出,propotype是构造函数(Person())的属性,而consructor则是构造函数的prototype属性所指向的那个对象,也就是说constuctor是原型对象的属性。constructor属性是定义在原型对象上面,意味着也可以被实例对象继承。

同样也可以得出结论,就是console.log(girl.construcotr); //Person() console.log(girl.construcotr == Person.propotype.construcotr); //true

而由于箭头函数的this往往指向的是最先一个闭包(作为函数,通常是window),所以箭头函数不能作为构造函数(因为构造函数要求指向实例化后this指向实例对象)。

原型链,简单理解就是原型组成的链,对象的__proto__它的是原型,而原型也是一个对象,也有__proto__属性,原型的__proto__又是原型的原型,就这样可以一直通过__proto__想上找,这就是原型链,当向上找找到Object的原型的时候,这条原型链就算到头了 原型链

instanceof是判断实例对象的__proto__和生成该实例的构造函数的prototype是不是引用的同一个地址。

原型链

new()操作到底做了什么

使用 new 操作符创建实例。以这种方式调用构造函数实际上会经历以下 4 个步骤:

  • 创建一个新对象;
  • 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象) ;
  • 执行构造函数中的代码(为这个新对象添加属性) ;
  • 返回新对象。
var obj = new Base();
// 等价于
var obj  = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
//随后通过Base.prototype.xxx = () => {}为Base添加方法时,obj也同时拥有此方法

三种引用类型(String Number Boolean)是没有属性和方法的,但仍然可以使用对象才有的属性方法。这时因为在对基本类型使用属性方法的时候,后台会隐式的创建这个基本类型的对象,之后再销毁这个对象

只有函数有prototype,对象是没有的。但是函数也是有__proto__的,因为函数也是对象。函数的__proto__指向的是Function.prototype。

继承的几种方式

from cnblog.com

  • 原型链继承
function Cat(){
}
Cat.prototype = new Animal();// Animal为父构造函数
Cat.prototype.name = 'cat';

// Cat为子构造函数
var cat = new Cat();
  • 构造继承
function Cat(name){
  Animal.call(this); //与原型链的区别
  this.name = name || 'Tom'; //可以传参
}
var cat = new Cat();
console.log(cat instanceof Animal); // false 实例只是子类的实例,而不是父类的
  • 实例继承
function Cat(name){
  var instance = new Animal(); //造成实例只是父类的实例,不是子类的
  instance.name = name || 'Tom';
  return instance;
}
var cat = new Cat(); //等价于var cat = Cat()
console.log(cat instanceof Cat); // false
  • 拷贝继承
function Cat(name){
  var animal = new Animal();
  for(var p in animal){
    Cat.prototype[p] = animal[p];
  }
  Cat.prototype.name = name || 'Tom';
}
var cat = new Cat();
console.log(cat instanceof Animal); // false
  • 组合继承
function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = new Animal(); //否则,实例就仅仅只是子类的实例
Cat.prototype.constructor = Cat; //修复构造函数的指向
var cat = new Cat();

Call、bind、apply

总结自来自简书-JavaScript中的call、apply、bind深入理解

  • Call:首先寻找call方法,最后通过原型链在Function的原型中找到call方法,然后让call方法执行,在执行call方法的时候,让fn方法中的this变为第一个参数值obj,最后再把fn这个函数执行,只是改变fn中的this的指向。
  • apply:作用与call类似,只是后面的参数用数组绑定
  • bind:同样会改变this的指向,但不会立即执行。

补充bind的特点

一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

bind原生js实现

github.com/mqyqingfeng

Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this; // 首先要改变this指向
    var args = Array.prototype.slice.call(arguments, 1); // 获取所有的参数

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs)); // 作为构造函数时,this指向实例;作为普通函数时,将绑定函数的this指向context
        // 传入参数
    }

    fNOP.prototype = this.prototype; // 修改返回函数的prototype为绑定函数的prototype
    fBound.prototype = new fNOP(); // 空函数周转,避免修改fBound.prototype值时会修改绑定函数的prototype
    return fBound;
}

跨域

  • jsonp
      var script = document.createElement('script');
      script.type = 'text/javascript';
    
      // 传参并指定回调执行函数为onBack
      script.src = 'http://www.domain-com:8080/login?user=admin&callback=onBack';
      document.head.appendChild(script);
    
      // 回调执行函数
      function onBack(res) {
          alert(JSON.stringify(res));
      }
    
  • document.domain + iframe跨域
      //父窗口:(http://www.domain.com/a.html)
      <iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
      <script>
          document.domain = 'domain.com';
          var user = 'admin';
      </script>
      //子窗口:(http://child.domain.com/b.html)
      <script>
          document.domain = 'domain.com';
          // 获取父窗口中变量
          alert('get js data from parent ---> ' + window.parent.user); //"admin"
      </script>
    
  • location.hash + iframe

    与document.domain类似,不同的是,通过修改父页面的iframe的src进而达到修改window.hash的效果,子页面通过window.onhashchange来监听

  • window.name + iframe跨域
  • 跨域资源共享(CORS)

    前端正常请求,后端设置:

      res.writeHead(200, {
          'Access-Control-Allow-Credentials': 'true',     // 后端允许发送Cookie
          'Access-Control-Allow-Origin': 'http://www.domain1.com',    // 允许访问的域(协议+域名+端口)
          /* 
              * 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现),
              * 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问
              */
          'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'  // HttpOnly的作用是让js无法读取cookie
      });
    
  • nginx代理跨域
  • nodejs中间件代理跨域
  • WebSocket协议跨域

同、异步与串、并行

同步异步的差别是同步是事情必须按照顺序执行,而异步则可以在例如发送请求,在等待请求返回的时候去做其他事。

串行和并行则是针对数据来说的,串行指数据传输是先后依次的,类似单行道,而并行则代表可以同时传输不同的数据,类似多行道

原生JavaScript实现Ajax

//Get请求

//步骤一:创建异步对象
var ajax = new XMLHttpRequest();
//步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
ajax.open('get','getStar.php?starName='+name);
//步骤三:发送请求
ajax.send();
//步骤四:注册事件 onreadystatechange 状态改变就会调用
ajax.onreadystatechange = function () {
if (ajax.readyState==4 &&ajax.status==200) {
    //步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的
    console.log(ajax.responseText);//输入相应的内容
  }
}

//POST请求

//创建异步对象  
var xhr = new XMLHttpRequest();
//设置请求的类型及url
//post请求一定要添加请求头才行不然会报错
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr.open('post', '02.post.php' );
//发送请求
xhr.send('name=fox&age=18');
xhr.onreadystatechange = function () {
        // 这步为判断服务器是否正确响应
    if (xhr.readyState == 4 && xhr.status == 200) {
        console.log(xhr.responseText);
    }
};

readyState

XMLHttpRequest.readyState属性返回XMLHttpRequest客户端所处的状态。一个XHR客户端存在已下几种状态:

  • 0,UNSENT

XMLHttpRequest客户端创建了,但open方法还没有被调用

  • 1,OPENED

open方法被调用。在这个状态过程中,请求头部可以使用setRequestHeader()方法设置,可以调用send方法,这个方法将发起获取。

  • 2,HEADERS_RECEIVED

send方法被调用,并且返回头部也已经收到

  • 3,LOADING

返回主体(response’s body)正在被接收。如果responseType是“text”或空字符串,则responseText在加载时将有部分文本响应。

  • 4,DONE

fetch操作完成。意味着数据传输已经完成或失败

Css

常见css布局面试

  • footer固定在底部,超过一屏自动撑开
html {
    height: 100%;
}
body {
    margin: 0;
    padding: 0;
    min-height: 100%;
    position: relative;
}
#footer{
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    color: #969696;
    text-align: center;
}
  • 多列等高

w3cplus.com

html代码,使用嵌套来实现等高

<div id="container2">
    <div id="container1">
        <div id="col1">Column 1</div>
        <div id="col2">Column 2</div>
    </div>
</div>

css代码

#container2 { 
    float: left; 
    width: 100%; 
    background: orange; 
    position: relative; 
    overflow: hidden; 
} 
#container1 { 
    float: left; 
    width: 100%; 
    background: green; 
    position: relative; 
    right: 30%; 
} 
#col1 { /*设置一个width把col2挤到右边去了*/
    width: 66%; 
    float: left; 
    position: relative; 
    left: 32%; 
} 
#col2 { 
    width: 26%; 
    float: left; 
    position: relative; 
    left: 36%; 
}
  • css画一个三角形 ```css .triangle_border_up{ width:0; height:0; border-width:0 30px 30px 30px; /上右下左 上为0为上三角形/ border-style:solid; border-color:transparent transparent #333;/透明 透明 灰/ margin:40px auto; position:relative; }
### flex布局
flex布局即弹性布局,设为 Flex 布局以后,子元素的float、clear和vertical-align属性将失效。

flex布局有六种属性:
- flex-direction:决定主轴的方向(即项目的排放方向)有row,row-reverse,column,column-reverse
- flex-wrap:定义一条轴线排不下时如何换行有nowrap,wrap,wrap-reverse(换行且第一行在下方)
- flex-flow:flex-direction和flex-wrap属性的简写形式,默认为`row || nowrap`
- justify-content:定义在主轴上的对齐方式,有flex-start,flex-end,center,space-between(贴这width两边),space-around(两边未贴两边)
- align-items:
- align-content:

实现同一个flex container中多种对其方式并存。特别适用于导航栏或表单栏,可将某些元素左对齐,另一些元素右对齐。
```css
// 首先,父元素设置flex布局
.nav{
    display: flex;
    flex-wrap: wrap;
}
// 然后左边的元素正常布局,为需要单独浮动到右边的元素设置
.right{
    margin-left: auto; // 设置margin-left为auto即将右边的所有位置都分给margin-left了,所有,元素就被挤到了最右边
} 

还有上下也类似。

垂直居中

  • display: flex
    .content{
      display: flex;
      justify-content:center;
      align-items:Center;
    }
    
  • 绝对定位和负边距
    .outer{position:relative;}
    .outer .content{
      position: absolute;
      width:100px;
      height: 50px;
      top:50%;
      left:50%;
      margin-left:-50px;//width的一半
      margin-top:-25px; //height的一半
      text-align: center;
    }
    
  • translate
    .outer{position:relative;}
    .outer .content{
      position: absolute;
      top:50%;
      left:50%;
      width: 100%;
      transform: translate(50%, 50%);
      text-align: center;
    }
    

translate、transform和translation,以及动画animation

  • translate:移动,是transform的一个方法。translate(x,y)表示在x轴y轴方向移动x,y个单位。
  • transform:变形,改变。rotate() 顺时针旋转给定的角度;skew() 元素翻转给定的角度,根据给定的水平线(x轴)和垂直线(Y轴)参数:skew(30deg,20deg);scale()放大或缩小,根据给定的宽度(X轴)和高度(Y轴)参数:scale(2,4);translate() 平移,传进x,y值,代表沿x轴和y轴平移的距离;matrix() 旋转、缩放移动以及倾斜元素matrix(scale.x, scale.y, translate.x, translate.y);改变起点位置 transform-origin : bottom left;
  • transition: 允许css属性值在一定的时间区间内平滑的过渡,即过渡动画。transition作用是指定了某一个属性(如width、left、transform等)在两个值之间如何过渡。
  • animation是指定keyframes的动画,具体是
animation:mymove 5s infinite;
-webkit-animation:mymove 5s infinite; /*Safari and Chrome*/
}

@keyframes mymove
{
    from {left:0px;}
    to {left:200px;}
}

margin

  • margin的大小问题:当两元素水平方向时,margin为左边右margin+右边左margin相加;当竖直方向时,margin为上面元素下margin和下面元素上margin中的最大值;当内容为空且设置有上下margin时,元素高度为上下margin中的最大值。
  • 父子元素中的margin:当给在父元素中,给子元素简单的添加一个margin-top时,是达不到预期效果的,(它会使父子元素整个向下偏移,而父子元素的相对位置却没有变),除了一些常规方法,可以为父元素添加overflow: hidden属性;margin-top的%值是相对于父元素的宽度而非高度。

盒模型

盒模型分为标准模型、IE模型 box-normal

box-sizing:content-box; 在标准模型中,盒模型的宽高只是content的宽高。 box-IE

box-sizing:border-box; IE模型中盒模型的宽高是content+padding+border的总宽高(Note,两种模型都没有把margin算在内)

BFC

BFC即Block Formatting context,块格式化上下文

BFC的创建方法

  • 根元素或其它包含它的元素;
  • 浮动 (元素的float不为none);
  • 绝对定位元素 (元素的position为absolute或fixed);
  • 行内块inline-blocks(元素的 display: inline-block);
  • 表格单元格(元素的display: table-cell,HTML表格单元格默认属性);
  • overflow的值不为visible的元素;
  • 弹性盒 flex boxes (元素的display: flex或inline-flex);

但其中,最常见的就是overflow:hiddenfloat:left/rightposition:absolute。也就是说,每次看到这些属性的时候,就代表了该元素以及创建了一个BFC了。

归纳起来就是

  • 内部的盒会在垂直方向一个接一个排列(可以看作BFC中有一个的常规流);
  • 处于同一个BFC中的元素相互影响,可能会发生margin collapse(margin塌陷);
  • 每个元素的margin box的左边,与容器块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此;
  • BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之亦然;
  • 计算BFC的高度时,考虑BFC所包含的所有元素,连浮动元素也参与计算;
  • 浮动盒区域不叠加到BFC上;

各种元素

  • 块级元素

总是在新行上开始;高度,行高以及外边距和内边距都可控制;宽度缺省是它的容器的100%,除非设定一个宽度。它可以容纳内联元素和其他块元素

常见的标签:article、aside、div、header、hr、li、ul、h1、p

  • 行内元素

和其他元素都在一行上;高,行高及外边距和内边距不可改变;宽度就是它的文字或图片的宽度,不可改变。内联元素只能容纳文本或者其他内联元素。可以设置line-height,无法设置上下margin和padding

常见的标签:a、br(上面hr是块级)、span、video、textarea

  • 替换元素

替换元素就是浏览器根据元素的标签和属性,来决定元素的具体显示内容。典型的有img、input、textarea、select、object

替换元素居中,可以设置line-height = height

  • 非替换元素

(X)HTML 的大多数元素是非替换元素,他们将内容直接告诉浏览器,将其显示出来。

React & Redux & webpack

Redux基础:

  • Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store
  • Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state
  • Store 就是把Action和Reducers联系到一起的对象。Store 有以下职责:
  1. 维持应用的 state;
  2. 提供 getState() 方法获取 state;
  3. 提供 dispatch(action) 方法更新 state;
  4. 通过 subscribe(listener) 注册监听器;
  5. 通过 subscribe(listener) 返回的函数注销监听器。

Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用combineReducers 来 reducer 组合 而不是创建多个 store。

Diff算法

from zhihu.com

  • treeDiff

React 通过 updateDepth 对 Virtual DOM 树进行层级控制,只会对相同颜色方框内的 DOM 节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。 diff-01

  • element diff

当节点处于同一层级时,React diff 提供了三种节点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)。

INSERT_MARKUP,新的 component 类型不在老集合里, 即是全新的节点,需要对新节点执行插入操作。

MOVE_EXISTING,在老集合有新 component 类型,且 element 是可更新的类型,generateComponentChildren 已调用 receiveComponent,这种情况下 prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点。

REMOVE_NODE,老 component 类型,在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者老 component 不在新集合里的,也需要执行删除操作。

  • 对于key的用法,如果没有key的话,根据diff算法,则当对比不对时,会直接删除旧的,追加新的 diff-02 而当为每个节点设置了唯一的key之后,则可以通过判断不同节点的key值,将节点不断后移即可。(注意是后移,因为在新集合之中,相对老集合处于后面位置的话,移动是不影响前面的,所以放一边,是一种优化) diff-03

redux中间件

中间件是允许我们扩展redux应用的功能,位于dispatch和reducers之间,意味着我们可以在数据流到达reducer之前修改我们的dispatch操作,或者在dispatch执行的过程中运行代码。

const customMiddleware = store => next => action => {
  ...
}

中间件是一个柯里化函数(一个函数返回另一个函数)。middleware接收一个store(redux的store),然后返回一个函数接收next函数(当已分配的任务被中间件完成时,调用next函数。它给我们的reducer或其他中间件发送actions),再返回另一个函数接收action函数(我们正在dispatch的action)

redux-thunk & redux-saga

from medium.com@shoshanarosenfield

我们为什么要用像redux-thunk这样的中间件?因为redux的store只支持同步的数据流;这时候中间件设计出来允许异步数据,解释所有的分发(dispatch),最后返回一个纯对象,恢复redux同步数据流,redux中间件可以解决很多关键的异步需求。

  • Redux-thunk

在redux上下文中,redux-thunk中间件允许你写一个action creators,返回一个函数,而不是通常的action对象,然后可以使用thunk来延迟action的dispatch,直到异步函数完成(例如,等到axios请求收到数据)。

Redux-thunk逐步过程的概括:

检查传进来的action,如果是普通的action对象,啥也不做;如果是函数,redux-thunk则会调用(invoke)它,并传入store的dispatch,getState等方法作为参数;在函数运行完后,thunk会dispatch action,相应的更新state。

Redux-thunk包含两部分,一个是thunk生成器,即一个生成thunk的action生成器;再一个是thunk本身,一个返回自thunk生成器并接收dispatch和setState作为参数的函数(thunk本身是个函数)

  • Redux-saga

Redux-saga旨在于使应用的副作用(像异步fetch数据)更好的处理,以及使执行更高效。其思想是,使saga分隔出来成为处理应用副作用的单独的线程;

与redux-thunk不同的是,可以使用普通的redux action从主应用程序来启动、暂停和取消一个的saga线程。和redux-thunk一样,saga也可以访问完整的redux应用状态(state),还可以调度(dispatch)redux的action。

Generators。redux-saga为完成这种功能,使用了ES6的新特性——Generator。Generator函数可以退出,并在随后重新进入的函数

总结起来,thunk和saga谁赢了?都没有,下面是两者实现同一功能的代码比较

Redux-thunk:redux-thunk Redux-saga:真的是用的generator函数呢 redux-saga

Saga的好处就是可以避免回调地狱,意味着可以避免传入函数,并在内部调用他们。此外,还可以更容易的测试异步数据流。调用和put方法返回js对象,因此简单的等价比较就可以测试saga函数返回的每一个值。redux-thunk返回promise,这很难测试。saga一票,函数结构好,容易测试

Thunk的优势就是简单,易于初学者,因为他所有的逻辑都包含在函数中。

webpack打包的过程

@taobaofed.org@medium.com

流程总览可以参考淘宝前端的图

webpack-process

webpack与其他工具像gulp和grunt有什么主要的不同

以下关于webpack的问题都来自于github.com

webpack是模块打包,这个工具为开发人员提供了分离模块的控制,允许他们根据特定的情况调整构建,并提供无法开箱即用的解决方案。

与Grunt比较,webpack给现代前端项目提供更加灵活和高级的功能。其一个核心功能就是可以使用特定的loaders(加载器)和plugins(插件)进行扩展。本质上,它用于将JavaScript文件与依赖项绑定到文件中,对于有大量非代码资源(图片、字体、css等等)的复杂JavaScript应用,webpack有更大的优势。

性能相比,Gulp和Grunt会查看与你的配置匹配的文件的已定义路径,而Webpack则分析整个项目。它检查所有依赖项,使用loaders(加载器)处理它们,并生成一个绑定的JS文件。也就是webpack会查看所有的依赖,而不仅仅只是当前需要打包的文件。

什么是webpack里的bundle

bundle是由webpack生成的输出文件。包含应用中所有用到的模块。bundle生成过程由webpack配置文件控制。

解决webpack打包文件体积过大

jianshu.com

  • 优化依赖引入
- import _ from 'lodash';
+ import _ from 'loadsh/core';
  • 提取第三方库
{
  entry: {
   bundle: 'app'
    vendor: ['react']
  }

  plugins: {
    new webpack.optimize.CommonsChunkPlugin('vendor',  'vendor.js')
  }
}
  • 代码压缩

webpack 自带了一个压缩插件 UglifyJsPlugin,只需要在配置文件中引入即可。

{
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ]
}
  • 代码分离

webpack.js.org。 以下是最基本的代码分离的操作 webpack-cs

  1. 防止重复

splitChunks插件可以帮助我们将公共依赖项提取到一个现有的条目块或一个全新的块中。让我们使用它来消除与前一个lodash依赖项的重复

webpack.config.js

  const path = require('path');

  module.exports = {
    mode: 'development',
    entry: {
      index: './src/index.js',
      another: './src/another-module.js'
    },
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
+   optimization: {
+     splitChunks: {
+       chunks: 'all'
+     }
+   }
  };
  1. 动态引入

webpack.js.org

Nodejs的特点及其使用场景

cnblog.com

首先其特点是:

  1. 它是一个Javascript运行环境
  2. 依赖于Chrome V8引擎进行代码解释
  3. 事件驱动
  4. 非阻塞I/O
  5. 轻量、可伸缩,适于实时数据交互应用
  6. 单进程,单线程

NodeJS解决的第一个瓶颈问题是并发连接,从单线程到多线程、线程池是有一个明显的提升的,但随着请求量的提升,仍然会带来排队和系统资源要求提升的窘境。nodejs采用异步和事件驱动模型,类似与餐厅点餐,我们点完餐后拿到了一个号码,拿到号码,我们往往会在位置上等待,而在我们后面的请求会继续得到处理,同样是拿了一个号码然后到一旁等待,接待员能一直进行处理。等到饭菜做号了,会喊号码,我们拿到了自己的饭菜,进行后续的处理(吃饭)

这个喊号码的动作在NodeJS中叫做回调(Callback),能在事件(烧菜,I/O)处理完成后继续执行后面的逻辑(吃饭),这体现了NodeJS的显著特点,异步机制、事件驱动

整个过程没有阻塞新用户的连接(点餐),也不需要维护已经点餐的用户与厨师的连接

NodeJS解决的另外一个问题是I/O阻塞,NodeJS遇到I/O事件会创建一个线程去执行,然后主线程会继续往下执行的。Java、PHP也有办法实现并行请求(子线程),但NodeJS通过回调函数(Callback)和异步机制会做得很自然

总而言之,NodeJS适合运用在高并发、I/O密集、少量业务逻辑的场景;不适合CPU密集型应用。

NodeJS异步编程

promise(),async+await

Nodejs异步io

对于异步IO的实现,其中有几个组成部分:事件循环、观察者、请求对象

  • 事件循环是node中的一种执行机制,这种机制是回调执行的基础部分,它保证了我们的回调函数能够被执行。

  • 观察者是暴露回调函数的窗口,如果整体的场景为饮料工厂的话,我们的瓶子就是我们的回调函数,事件循环就是传送带在那一直转,而观察者就是瓶子就如机器的入口,机器就是我们的应用程序。所以应用程序从观察者这里获取事件,应用程序询问观察者是否还有事件。

  • 请求对象,是应用程序封装的一个对象,里边包含了要做的IO操作类型,以及回调函数。

异步调用开始之后,应用程序封装一个请求对象,送入我们的线程池中的某个线程,该线程和操作系统的非阻塞IO通过epoll机制进行工作,这其中,会有观察者在线程池中进行检查,当某个线程的IO操作完成之后,观察者会将回调函数(封装在请求对象中的)放在事件循环上(上段提到的传送带),然后主线程调用回调函数。

进程间通信

  • 管道通信

速度慢,容量有限,只有父子进程能通讯

  • 命名管道

任何进程间都能通讯,但速度慢

  • 消息队列

容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题    

  • 信号量

不能传递复杂消息,只能用来同步    

  • 共享内存区

能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

尾递归

首先,普通递归由于不断地循环调用,并且导致前一个函数的值必须依赖与后一个值的返回(这种情况下的函数最后一步往往不是返回递归函数),所以会导致爆栈的情况发生;而尾递归,通过将一些方法,使函数调用的最后一步是返回递归函数,这样,在调用下一个函数时,因为下一个函数的返回值就是本函数的值,所以就可以将本函数出栈。 from cnblogs.com

int func(int n)
{
    if (n <= 1) return 1;
    return (n * func(n-1)); //很显然,最后不是返回函数
}
//尾递归
int tail_func(int n, int res)
{
     if (n <= 1) return res;

     return tail_func(n - 1, n * res); //返回函数
}
Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License.