前端面试题总结

69 minute read

前端面试题总结-01

计算机网络总结

常见代码题

Html及浏览器

HTML新增标签

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

HTML新特性

  • 语义化标签
  • 脚本和链接无需type
  • 正则表达式 (Regular Expressions),在HTML4或XHTML中,你需要用一些正规表达式来验证特定的文本。而HTML5中新的pattern属性让我们能够在标签处直接插入一个正规表达式。
      <input type="text" name="username" id="username" placeholder="4 <> 10" pattern="[A-Za-z]{4,10}" autofocus required>
    
  • web存储,localStoragesessionStorage
  • WebSocket,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
  • Web Workers,web worker是运行在后台的JavaScript,独立于其他脚本,不会影响页面的性能。你可以继续做任何愿意做的事情:点击、选取内容等等,而此时 web worker 在后台运行。关于web worker的应用大概分为三个部分:
    1. 创建web worker文件,worker文件是一个单独的js文件,写好逻辑后,通过postMessage()方法吧数据发送出去
    2. 调用页面创建worker对象,var w = new Worker(“worker文件路径”).然后通过实例对象调用onmessage事件进行监听,并获取worker文件里返回的数据
    3. 终止web worker,当我们的web worker创建后会持续的监听它,需要中止的时候则使用实例上的方法w.terminate()。

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

  • 起始行

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

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

  • 头部

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

DOCTYPE标签的作用

DOCTYPE标签是一种标准通用标记语言的文档类型声明,它的目的是要告诉标准通用标记语言解析器,它应该使用什么样的文档类型定义(DTD)来解析文档。

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对象返回,主要包括状态码,响应头,响应报文三个部分。

  • 页面渲染

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

    减少reflow/repaint的措施:

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

常见状态码

  • 1xx:指示信息–表示请求已接收,继续处理。
  • 2xx:成功–表示请求已被成功接收、理解、接受。
  • 3xx:重定向–要完成请求必须进行更进一步的操作。
    1. 301:永久重定向,请求的资源已经被永久的移动到新URI,今后任何新的请求都应使用新的URI代替。
    2. 302:临时性重定向,资源只是临时被移动,客户端应继续使用原有URI。
    3. 304:缓存中读取,未修改。所请求的资源未修改,服务器返回状态码时,不返回任何资源,客户端通常会缓存访问过的资源。
  • 4xx:客户端错误–请求有语法错误或请求无法实现。
    1. 400:请求报文中存在语法错误
    2. 401:请求未经授权,需要有同感HTTP认证的认证信息
    3. 403:服务器收到请求,但是拒绝提供服务
    4. 404:无法找到请求资源
  • 5xx:服务器端错误–服务器未能实现合法的请求。
    1. 500:服务器在执行时发生错误
    2. 503:服务器处于超负荷或者正在进行停机维护

DNS的过程

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

http、https、http2

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

HTTPS将HTTP协议数据包放到SSL/TSL层加密后,在TCP/IP层组成IP数据报去传输,以此保证传输数据的安全;而对于接收端,在SSL/TSL将接收的数据包解密之后,将数据传给HTTP协议层,就是普通的HTTP数据。

http2:http2有怎样的新特性呢,

  1. HTTP2.0的协议解析决定采用二进制格式;
  2. 多路复用,即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面
  3. 流控制,是一种阻止发送方向接收方发送大量数据的机制,以免超出后者的需求或处理能力:发送方可能非常繁忙、处于较高的负载之下,也可能仅仅希望为特定数据流分配固定量的资源。 例如,客户端可能请求了一个具有较高优先级的大型视频流,但是用户已经暂停视频,客户端现在希望暂停或限制从服务器的传输,以免提取和缓冲不必要的数据。
  4. 服务器推送,服务器可以对一个客户端请求发送多个响应。 换句话说,除了对最初请求的响应外,服务器还可以向客户端推送额外资源,而无需客户端明确地请求。

xss和csrf

  • 跨站脚本xss(cross site script)

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

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

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

csrf

  • 预防csrf
    1. 验证 HTTP Referer 字段,访问请求时要求携带referer值,此时便可以保证提交访问的是正规的网站
    2. 在请求地址中添加 token 并验证,可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。

Token

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

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

Web App 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引用数据类型

对象、数组、函数

JS基本类型和引用类型有什么区别呢?首先是赋值时候的差别,将引用数据类型,顾名思义,引用最常用的搭配是引用指针,因此引用类型的赋值其实是对象保存在栈区地址指针的赋值,因此两个变量指向同一个对象;相应的基本数据类型,则是会分配一块新的内存空间,因此两个变量是没有关联的。

再一个区别在于比较,基本类型存储在栈区中,在比较的时候是值的比较,因此无论是==还是===都会返回true(当然像1===true还是会返回false);而引用类型的存储需要内存的栈区和堆区(堆区是指内存里的堆内存)共同完成,栈区内存保存变量标识符和指向堆内存中该对象的指针,也可以说是该对象在堆内存的地址;说过引用类型是按引用访问的,换句话说就是比较两个对象的堆内存中的地址是否相同,因此即使两个引用类型的类型一样、值一样,但地址肯定是不一样的,所以在比较时会返回false

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时会报错。

position状态总结

static; inherit; relative; absolute; fixed; sticky;

relative:相对于自己的初始位置,不脱离文档流。也就是说元素框偏移某个距离,元素仍保持其未定位前的形状,它原本所占的空间仍保留。

absolute:元素的位置相对于最近的已定位祖先元素,如果元素没有已定位的祖先元素,那么它的位置相对于最初的包含块。

fixed:元素脱离正常的文档流,所以它与absolute元素很相似,同样会被周围元素忽略,支持top,bottom,left,right属性,但fixed 元素正如它的名字一样,它是固定在屏幕的某个位置,它不会随着浏览器滚动条的滚动而一起滚动。

sticky:表现为position:relative和position:fixed的结合体——当元素在屏幕内,表现为relative,就要滚出显示器屏幕的时候,表现为fixed。

常见算法题

常见代码题

async await、Promise、setTimeout

【题目】

async function async1(){
   console.log('async1 start');
    await async2();
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}
console.log('script start');
setTimeout(function(){
    console.log('setTimeout')
},0);
async1();
new Promise(function(resolve){
    console.log('promise1');
    resolve();
}).then(function(){
    console.log('promise2')
});
console.log('script end')
// script start
// async1 start
// async2
// promise1
// script end
// promise2
// async1 end
// setTimeout

async、promise、setTimeout的优先级也就是执行顺序是怎么样的?首先可以很清楚的知道script start最先输出,它是直接添加到消息队列里的,然后 promise 的优先级比 setTimeOut高,其中 promise的 then和 setTimeout都是依次加入到消息队列后面去的。然后看 async函数,阮一峰老师的说法是 async函数返回一个 promise对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

所以前三个没有问题,然后为什么第四个是promise1呢?其实我也不太清楚,可能是await后面的代码都被加到了 promise then队列的最后面;再到后面 promise的任务都执行完了,在执行script end然后后面的依次输出。

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的执行时间往往大于设定的时间,因为每次插入到后续队列,都需要等待代码执行完成。

setTimeout计时器不准的问题

var count = 1000;
function animate(t) {
  var start = +new Date();
  count--;
  var finish = +new Date();
  setTimeout(animate, t - (finish-start));
}
animate(1000);

首屏渲染优化

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
      // jsonp请求 
      params = params || {}; 
      params.data = params.data || {}; 
      var json = params.jsonp ? jsonp(params) : json(params); 
      function jsonp(params) { 
        //创建script标签并加入到页面中 
        var callbackName = params.jsonp;
        var head = document.getElementsByTagName('head')[0];
        // 设置传递给后台的回调参数名 
        params.data['callback'] = callbackName;
        var data = formatParams(params.data);
        var script = document.createElement('script'); 
        head.appendChild(script); 
        //创建jsonp回调函数 
        window[callbackName] = function(json) { 
          head.removeChild(script); 
          clearTimeout(script.timer); 
          window[callbackName] = null; 
          params.success && params.success(json); 
        };
        //发送请求 
        script.src = params.url + '?' + data;
        //为了得知此次请求是否成功,设置超时处理 
        if(params.time) {
          script.timer = setTimeout(function() {
            window[callbackName] = null; 
            head.removeChild(script); 
            params.error && params.error({ 
            message: '超时' 
          });
          }, time); 
        } 
      };
      //格式化参数 
      function formatParams(data) { 
        var arr = [];
        for(var name in data) { 
          arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name])); 
        };
        // 添加一个随机数,防止缓存 
        arr.push('v=' + random()); 
        return arr.join('&'); 
      }
    
  • 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协议跨域

    HTTP访问控制(CORS)

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。

  • 简单请求,某些请求不会触发cors预检请求,这样的请求为“简单请求”。若请求满足所有下述条件,则该请求可视为“简单请求”:
    • 使用GETHEADPOST之一的方法。
    • 首部字段仅包含AcceptAccept-LanguageContent-LanguageContent-Type (需要注意额外的限制)DPRDownlinkSave-DataViewport-WidthWidth
    • Content-Type的值仅限于text/plainmultipart/form-dataapplication/x-www-form-urlencoded
  • 预检请求,与前述简单请求不同,“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。”预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。简单的理解就是询问服务器即将发起的请求是否合法

详解JavaScript中的Event Loop(事件循环)机制

Javascript中的事件循环

同、异步与串、并行

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

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

原生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画一个三角形
.triangle_border_up{
    width:0;
    height:0;
    border-width:0 30px 30px 30px; /*上右下左 上为0为上三角形*/
    border-style:solid;
    border-color:transparent transparent #333;/*透明 透明  灰*/
}

css样式优先级

优先级关系:内联样式 > ID 选择器 > 类选择器 = 属性选择器(如a[href=”segmentfault.com”]) = 伪类选择器(如:hover{}) > 标签选择器 = 伪元素选择器(如 ::before{})

内联样式比其他任何选择器优先级都高

当在一个样式声明中使用一个!important 规则时,此声明将覆盖任何其他声明。

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中多种对其方式并存。特别适用于导航栏或表单栏,可将某些元素左对齐,另一些元素右对齐。

// 首先,父元素设置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 的大多数元素是非替换元素,他们将内容直接告诉浏览器,将其显示出来。

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.