关于前端:美团前端面试题集锦

4次阅读

共计 13812 个字符,预计需要花费 35 分钟才能阅读完成。

HTML5 有哪些更新

1. 语义化标签

  • header:定义文档的页眉(头部);
  • nav:定义导航链接的局部;
  • footer:定义文档或节的页脚(底部);
  • article:定义文章内容;
  • section:定义文档中的节(section、区段);
  • aside:定义其所处内容之外的内容(侧边);

2. 媒体标签

(1)audio:音频

<audio src=''controls autoplay loop='true'></audio>

属性:

  • controls 控制面板
  • autoplay 自动播放
  • loop=‘true’循环播放

(2)video 视频

<video src=''poster='imgs/aa.jpg' controls></video>

属性:

  • poster:指定视频还没有齐全下载结束,或者用户还没有点击播放前显示的封面。默认显示以后视频文件的第一针画面,当然通过 poster 也能够本人指定。
  • controls 控制面板
  • width
  • height

(3)source 标签
因为浏览器对视频格式反对水平不一样,为了可能兼容不同的浏览器,能够通过 source 来指定视频源。

<video>
     <source src='aa.flv' type='video/flv'></source>
     <source src='aa.mp4' type='video/mp4'></source>
</video>

3. 表单

表单类型:

  • email:可能验证以后输出的邮箱地址是否非法
  • url:验证 URL
  • number:只能输出数字,其余输出不了,而且自带高低增大减小箭头,max 属性能够设置为最大值,min 能够设置为最小值,value 为默认值。
  • search:输入框前面会给提供一个小叉,能够删除输出的内容,更加人性化。
  • range:能够提供给一个范畴,其中能够设置 max 和 min 以及 value,其中 value 属性能够设置为默认值
  • color:提供了一个色彩拾取器
  • time:时分秒
  • data:日期抉择年月日
  • datatime:工夫和日期(目前只有 Safari 反对)
  • datatime-local:日期工夫控件
  • week:周控件
  • month:月控件

表单属性:

  • placeholder:提示信息
  • autofocus:主动获取焦点
  • autocomplete=“on”或者 autocomplete=“off”应用这个属性须要有两个前提:

    • 表单必须提交过
    • 必须有 name 属性。
  • required:要求输入框不能为空,必须有值才可能提交。
  • pattern=” ” 外面写入想要的正则模式,例如手机号 patte=”^(+86)?\d{10}$”
  • multiple:能够抉择多个文件或者多个邮箱
  • form=” form 表单的 ID”

表单事件:

  • oninput 每当 input 里的输入框内容发生变化都会触发此事件。
  • oninvalid 当验证不通过时触发此事件。

4. 进度条、度量器

  • progress 标签:用来示意工作的进度(IE、Safari 不反对),max 用来示意工作的进度,value 示意已实现多少
  • meter 属性:用来显示残余容量或残余库存(IE、Safari 不反对)

    • high/low:规定被视作高 / 低的范畴
    • max/min:规定最大 / 小值
    • value:规定以后度量值

设置规定:min < low < high < max

5.DOM 查问操作

  • document.querySelector()
  • document.querySelectorAll()

它们抉择的对象能够是标签,能够是类(须要加点),能够是 ID(须要加 #)

6. Web 存储

HTML5 提供了两种在客户端存储数据的新办法:

  • localStorage – 没有工夫限度的数据存储
  • sessionStorage – 针对一个 session 的数据存储

7. 其余

  • 拖放:拖放是一种常见的个性,即抓取对象当前拖到另一个地位。设置元素可拖放:
<img draggable="true" />
  • 画布(canvas):canvas 元素应用 JavaScript 在网页上绘制图像。画布是一个矩形区域,能够管制其每一像素。canvas 领有多种绘制门路、矩形、圆形、字符以及增加图像的办法。
<canvas id="myCanvas" width="200" height="100"></canvas>
  • SVG:SVG 指可伸缩矢量图形,用于定义用于网络的基于矢量的图形,应用 XML 格局定义图形,图像在放大或扭转尺寸的状况下其图形品质不会有损失,它是万维网联盟的规范
  • 天文定位:Geolocation(天文定位)用于定位用户的地位。‘

总结:(1)新增语义化标签:nav、header、footer、aside、section、article
(2)音频、视频标签:audio、video
(3)数据存储:localStorage、sessionStorage
(4)canvas(画布)、Geolocation(天文定位)、websocket(通信协议)
(5)input 标签新增属性:placeholder、autocomplete、autofocus、required
(6)history API:go、forward、back、pushstate

移除的元素有:

  • 纯体现的元素:basefont,big,center,font, s,strike,tt,u;
  • 对可用性产生负面影响的元素:frame,frameset,noframes;

写代码:实现函数可能深度克隆根本类型

浅克隆:

function shallowClone(obj) {let cloneObj = {};

  for (let i in obj) {cloneObj[i] = obj[i];
  }

  return cloneObj;
}

深克隆:

  • 思考根底类型
  • 援用类型

    • RegExp、Date、函数 不是 JSON 平安的
    • 会失落 constructor,所有的构造函数都指向 Object
    • 破解循环援用
function deepCopy(obj) {if (typeof obj === 'object') {var result = obj.constructor === Array ? [] : {};

    for (var i in obj) {result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
    }
  } else {var result = obj;}

  return result;
}

HTTP 状态码 304 是多好还是少好

服务器为了进步网站访问速度,对之前拜访的局部页面指定缓存机制,当客户端在此对这些页面进行申请,服务器会依据缓存内容判断页面与之前是否雷同,若雷同便间接返回 304,此时客户端调用缓存内容,不用进行二次下载。

状态码 304 不应该认为是一种谬误,而是对客户端 有缓存状况下 服务端的一种响应。

搜索引擎蜘蛛会更加青眼内容源更新频繁的网站。通过特定工夫内对网站抓取返回的状态码来调节对该网站的抓取频次。若网站在肯定工夫内始终处于 304 的状态,那么蜘蛛可能会升高对网站的抓取次数。相同,若网站变动的频率十分之快,每次抓取都能获取新内容,那么与日俱增,的回访率也会进步。

产生较多 304 状态码的起因:

  • 页面更新周期长或不更新
  • 纯动态页面或强制生成动态 html

304 状态码呈现过多会造成以下问题:

  • 网站快照进行;
  • 收录缩小;
  • 权重降落。

代码输入后果

var a, b
(function () {console.log(a);
   console.log(b);
   var a = (b = 3);
   console.log(a);
   console.log(b);   
})()
console.log(a);
console.log(b);

输入后果:

undefined 
undefined 
3 
3 
undefined 
3

这个题目和下面题目考查的知识点相似,b 赋值为 3,b 此时是一个全局变量,而将 3 赋值给 a,a 是一个局部变量,所以最初打印的时候,a 仍旧是 undefined。

说下对 JS 的理解吧

是基于原型的动静语言,次要独特个性有 this、原型和原型链。

JS 严格意义上来说分为:语言规范局部(ECMAScript)+ 宿主环境局部

语言规范局部

2015 年公布 ES6,引入诸多新个性使得可能编写大型项目变成可能,规范自 2015 之后以年号代号,每年一更

宿主环境局部

  • 在浏览器宿主环境包含 DOM + BOM 等
  • 在 Node,宿主环境包含一些文件、数据库、网络、与操作系统的交互等

代码输入后果

function foo() {console.log( this.a);
}

function doFoo() {foo();
}

var obj = {
  a: 1,
  doFoo: doFoo
};

var a = 2; 
obj.doFoo()

输入后果:2

在 Javascript 中,this 指向函数执行时的以后对象。在执行 foo 的时候,执行环境就是 doFoo 函数,执行环境为全局。所以,foo 中的 this 是指向 window 的,所以会打印出 2。

参考 前端进阶面试题具体解答

vue-router

vue-router 是 vuex.js 官网的路由管理器,它和 vue.js 的外围深度集成,让构建但页面利用变得大海捞针

<router-link> 组件反对用户在具备路由性能的利用中 (点击) 导航。通过 to 属性指定指标地址

<router-view> 组件是一个 functional 组件,渲染门路匹配到的视图组件。<keep-alive> 组件是一个用来缓存组件

router.beforeEach

router.afterEach

to: Route: 行将要进入的指标 路由对象

from: Route: 以后导航正要来到的路由

next: Function: 肯定要调用该办法来 resolve 这个钩子。执行成果依赖 next 办法的调用参数。介绍了路由守卫及用法,在我的项目中路由守卫起到的作用等等

Cookie、LocalStorage、SessionStorage 区别

浏览器端罕用的存储技术是 cookie、localStorage 和 sessionStorage。

  • cookie: 其实最开始是服务器端用于记录用户状态的一种形式,由服务器设置,在客户端存储,而后每次发动同源申请时,发送给服务器端。cookie 最多能存储 4 k 数据,它的生存工夫由 expires 属性指定,并且 cookie 只能被同源的页面访问共享。
  • sessionStorage: html5 提供的一种浏览器本地存储的办法,它借鉴了服务器端 session 的概念,代表的是一次会话中所保留的数据。它个别可能存储 5M 或者更大的数据,它在以后窗口敞开后就生效了,并且 sessionStorage 只能被同一个窗口的同源页面所访问共享。
  • localStorage: html5 提供的一种浏览器本地存储的办法,它个别也可能存储 5M 或者更大的数据。它和 sessionStorage 不同的是,除非手动删除它,否则它不会生效,并且 localStorage 也只能被同源页面所访问共享。

下面几种形式都是存储大量数据的时候的存储形式,当须要在本地存储大量数据的时候,咱们能够应用浏览器的 indexDB 这是浏览器提供的一种本地的数据库存储机制。它不是关系型数据库,它外部采纳对象仓库的模式存储数据,它更靠近 NoSQL 数据库。

懒加载与预加载的区别

这两种形式都是进步网页性能的形式,两者次要区别是一个是提前加载,一个是缓慢甚至不加载。懒加载对服务器前端有肯定的缓解压力作用,预加载则会减少服务器前端压力。

  • 懒加载也叫提早加载,指的是在长网页中提早加载图片的机会,当用户须要拜访时,再去加载,这样能够进步网站的首屏加载速度,晋升用户的体验,并且能够缩小服务器的压力。它实用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的 src 属性设置为空字符串,将图片的实在门路保留在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出实在门路赋值给图片的 src 属性,以此来实现图片的提早加载。
  • 预加载指的是将所需的资源提前申请加载到本地,这样前面在须要用到时就间接从缓存取资源。 通过预加载可能缩小用户的等待时间,进步用户的体验。我理解的预加载的最罕用的形式是应用 js 中的 image 对象,通过为 image 对象来设置 scr 属性,来实现图片的预加载。

如何优化动画?

对于如何优化动画,咱们晓得,个别状况下,动画须要频繁的操作 DOM,就就会导致页面的性能问题,咱们能够将动画的 position 属性设置为 absolute 或者fixed,将动画脱离文档流,这样他的回流就不会影响到页面了。

HTML5 的离线贮存怎么应用,它的工作原理是什么

离线存储指的是:在用户没有与因特网连贯时,能够失常拜访站点或利用,在用户与因特网连贯时,更新用户机器上的缓存文件。

原理:HTML5 的离线存储是基于一个新建的 .appcache 文件的缓存机制(不是存储技术),通过这个文件上的解析清单离线存储资源,这些资源就会像 cookie 一样被存储了下来。之后当网络在处于离线状态下时,浏览器会通过被离线存储的数据进行页面展现

应用办法:(1)创立一个和 html 同名的 manifest 文件,而后在页面头部退出 manifest 属性:

<html lang="en" manifest="index.manifest">

(2)在 cache.manifest 文件中编写须要离线存储的资源:

CACHE MANIFEST
    #v0.11
    CACHE:
    js/app.js
    css/style.css
    NETWORK:
    resourse/logo.png
    FALLBACK:
    / /offline.html
  • CACHE: 示意须要离线存储的资源列表,因为蕴含 manifest 文件的页面将被主动离线存储,所以不须要把页面本身也列出来。
  • NETWORK: 示意在它上面列出来的资源只有在在线的状况下能力拜访,他们不会被离线存储,所以在离线状况下无奈应用这些资源。不过,如果在 CACHE 和 NETWORK 中有一个雷同的资源,那么这个资源还是会被离线存储,也就是说 CACHE 的优先级更高。
  • FALLBACK: 示意如果拜访第一个资源失败,那么就应用第二个资源来替换他,比方下面这个文件示意的就是如果拜访根目录下任何一个资源失败了,那么就去拜访 offline.html。

(3)在离线状态时,操作 window.applicationCache 进行离线缓存的操作。

如何更新缓存:

(1)更新 manifest 文件

(2)通过 javascript 操作

(3)革除浏览器缓存

注意事项:

(1)浏览器对缓存数据的容量限度可能不太一样(某些浏览器设置的限度是每个站点 5MB)。

(2)如果 manifest 文件,或者外部列举的某一个文件不能失常下载,整个更新过程都将失败,浏览器持续全副应用老的缓存。

(3)援用 manifest 的 html 必须与 manifest 文件同源,在同一个域下。

(4)FALLBACK 中的资源必须和 manifest 文件同源。

(5)当一个资源被缓存后,该浏览器间接申请这个绝对路径也会拜访缓存中的资源。

(6)站点中的其余页面即便没有设置 manifest 属性,申请的资源如果在缓存中也从缓存中拜访。

(7)当 manifest 文件产生扭转时,资源申请自身也会触发更新。

代码输入后果

var A = {n: 4399};
var B =  function(){this.n = 9999};
var C =  function(){var n = 8888};
B.prototype = A;
C.prototype = A;
var b = new B();
var c = new C();
A.n++
console.log(b.n);
console.log(c.n);

输入后果:9999 4400

解析:

  1. console.log(b.n),在查找 b.n 是首先查找 b 对象本身有没有 n 属性,如果没有会去原型(prototype)上查找,当执行 var b = new B()时,函数外部 this.n=9999(此时 this 指向 b) 返回 b 对象,b 对象有本身的 n 属性,所以返回 9999。
  2. console.log(c.n),同理,当执行 var c = new C()时,c 对象没有本身的 n 属性,向上查找,找到原型(prototype)上的 n 属性,因为 A.n++(此时对象 A 中的 n 为 4400),所以返回 4400。

浏览器的渲染过程

浏览器渲染次要有以下步骤:

  • 首先解析收到的文档,依据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的。
  • 而后对 CSS 进行解析,生成 CSSOM 规定树。
  • 依据 DOM 树和 CSSOM 规定树构建渲染树。渲染树的节点被称为渲染对象,渲染对象是一个蕴含有色彩和大小等属性的矩形,渲染对象和 DOM 元素绝对应,但这种对应关系不是一对一的,不可见的 DOM 元素不会被插入渲染树。还有一些 DOM 元素对应几个可见对象,它们个别是一些具备简单构造的元素,无奈用一个矩形来形容。
  • 当渲染对象被创立并增加到树中,它们并没有地位和大小,所以当浏览器生成渲染树当前,就会依据渲染树来进行布局(也能够叫做回流)。这一阶段浏览器要做的事件是要弄清楚各个节点在页面中的确切地位和大小。通常这一行为也被称为“主动重排”。
  • 布局阶段完结后是绘制阶段,遍历渲染树并调用渲染对象的 paint 办法将它们的内容显示在屏幕上,绘制应用 UI 根底组件。

大抵过程如图所示:

留神: 这个过程是逐渐实现的,为了更好的用户体验,渲染引擎将会尽可能早的将内容出现到屏幕上,并不会等到所有的 html 都解析实现之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

代码输入后果

// a
function Foo () {getName = function () {console.log(1);
 }
 return this;
}
// b
Foo.getName = function () {console.log(2);
}
// c
Foo.prototype.getName = function () {console.log(3);
}
// d
var getName = function () {console.log(4);
}
// e
function getName () {console.log(5);
}

Foo.getName();           // 2
getName();               // 4
Foo().getName();         // 1
getName();               // 1 
new Foo.getName();       // 2
new Foo().getName();     // 3
new new Foo().getName(); // 3

输入后果:2 4 1 1 2 3 3

解析:

  1. Foo.getName(), Foo 为一个函数对象,对象都能够有属性,b 处定义 Foo 的 getName 属性为函数,输入 2;
  2. getName(), 这里看 d、e 处,d 为函数表达式,e 为函数申明,两者区别在于变量晋升,函数申明的 5 会被后边函数表达式的 4 笼罩;
  3. Foo().getName(), 这里要看 a 处,在 Foo 外部将全局的 getName 从新赋值为 console.log(1) 的函数,执行 Foo()返回 this,这个 this 指向 window,Foo().getName() 即为 window.getName(),输入 1;
  4. getName(), 下面 3 中,全局的 getName 曾经被从新赋值,所以这里仍然输入 1;
  5. new Foo.getName(), 这里等价于 new (Foo.getName()),先执行 Foo.getName(),输入 2,而后 new 一个实例;
  6. new Foo().getName(), 这 里等价于 (new Foo()).getName(), 先 new 一个 Foo 的实例,再执行这个实例的 getName 办法,然而这个实例自身没有这个办法,所以去原型链__protot__上边找,实例.protot === Foo.prototype,所以输入 3;
  7. new new Foo().getName(), 这里等价于 new (new Foo().getName()),如上述 6,先输入 3,而后 new 一个 new Foo().getName() 的实例。

什么状况会阻塞渲染?

首先渲染的前提是生成渲染树,所以 HTML 和 CSS 必定会阻塞渲染。如果你想渲染的越快,你越应该升高一开始须要渲染的文件大小,并且扁平层级,优化选择器。而后当浏览器在解析到 script 标签时,会暂停构建 DOM,实现后才会从暂停的中央从新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都倡议将 script 标签放在 body 标签底部的起因。

当然在当下,并不是说 script 标签必须放在底部,因为你能够给 script 标签增加 defer 或者 async 属性。当 script 标签加上 defer 属性当前,示意该 JS 文件会并行下载,然而会放到 HTML 解析实现后程序执行,所以对于这种状况你能够把 script 标签放在任意地位。对于没有任何依赖的 JS 文件能够加上 async 属性,示意 JS 文件下载和解析不会阻塞渲染。

0.1 + 0.2 === 0.3 嘛?为什么?

JavaScript 应用 Number 类型来示意数字(整数或浮点数),遵循 IEEE 754 规范,通过 64 位来示意一个数字(1 + 11 + 52)

  • 1 符号位,0 示意负数,1 示意正数 s
  • 11 指数位(e)
  • 52 尾数,小数局部(即有效数字)

最大平安数字:Number.MAX_SAFE_INTEGER = Math.pow(2, 53) – 1,转换成整数就是 16 位,所以 0.1 === 0.1,是因为通过 toPrecision(16) 去无效位之后,两者是相等的。

在两数相加时,会先转换成二进制,0.1 和 0.2 转换成二进制的时候尾数会产生有限循环,而后进行对阶运算,JS 引擎对二进制进行截断,所以造成精度失落。

所以总结:精度失落可能呈现在进制转换和对阶运算中

常见的浏览器内核比拟

  • Trident: 这种浏览器内核是 IE 浏览器用的内核,因为在晚期 IE 占有大量的市场份额,所以这种内核比拟风行,以前有很多网页也是依据这个内核的规范来编写的,然而实际上这个内核对真正的网页规范反对不是很好。然而因为 IE 的高市场占有率,微软也很长时间没有更新 Trident 内核,就导致了 Trident 内核和 W3C 规范脱节。还有就是 Trident 内核的大量 Bug 等平安问题没有失去解决,加上一些专家学者公开本人认为 IE 浏览器不平安的观点,使很多用户开始转向其余浏览器。
  • Gecko: 这是 Firefox 和 Flock 所采纳的内核,这个内核的长处就是功能强大、丰盛,能够反对很多简单网页成果和浏览器扩大接口,然而代价是也不言而喻就是要耗费很多的资源,比方内存。
  • Presto: Opera 已经采纳的就是 Presto 内核,Presto 内核被称为公认的浏览网页速度最快的内核,这得益于它在开发时的天生劣势,在解决 JS 脚本等脚本语言时,会比其余的内核快 3 倍左右,毛病就是为了达到很快的速度而丢掉了一部分网页兼容性。
  • Webkit: Webkit 是 Safari 采纳的内核,它的长处就是网页浏览速度较快,尽管不迭 Presto 然而也胜于 Gecko 和 Trident,毛病是对于网页代码的容错性不高,也就是说对网页代码的兼容性较低,会使一些编写不规范的网页无奈正确显示。WebKit 前身是 KDE 小组的 KHTML 引擎,能够说 WebKit 是 KHTML 的一个开源的分支。
  • Blink: 谷歌在 Chromium Blog 上发表博客,称将与苹果的开源浏览器外围 Webkit 各奔前程,在 Chromium 我的项目中研发 Blink 渲染引擎(即浏览器外围),内置于 Chrome 浏览器之中。其实 Blink 引擎就是 Webkit 的一个分支,就像 webkit 是 KHTML 的分支一样。Blink 引擎当初是谷歌公司与 Opera Software 独特研发,下面提到过的,Opera 弃用了本人的 Presto 内核,退出 Google 营垒,追随谷歌一起研发 Blink。

Set,Map 解构

ES6 提供了新的数据结构 Set。它相似于数组,然而成员的值都是惟一的,没有反复的值。Set 自身是一个构造函数,用来生成 Set 数据结构。ES6 提供了 Map 数据结构。它相似于对象,也是键值对的汇合,然而“键”的范畴不限于字符串,各种类型的值(包含对象)都能够当作键。

Vue 通信

1.props 和 $emit
2. 地方事件总线 EventBus(根本不必)
3.vuex(官网举荐状态管理器)
4.$parent 和 $children
当然还有一些其余方法,但根本不罕用,或者用起来太简单来。介绍来通信的形式,还能够扩大说一下应用
场景,如何应用,注意事项之类的。

JS 闭包,你理解多少?

应该有面试官问过你:

  1. 什么是闭包?
  2. 闭包有哪些理论使用场景?
  3. 闭包是如何产生的?
  4. 闭包产生的变量如何被回收?

这些问题其实都能够被看作是同一个问题,那就是面试官在问你:你对 JS 闭包理解多少?

来总结一下我听到过的答案,尽量齐全还原候选人面试的时候说的原话。

答案 1: 就是一个 function 外面 return 了一个子函数,子函数拜访了里面那个函数的变量。

答案 2: for 循环外面能够用闭包来解决问题。

for(var i = 0; i < 10; i++){setTimeout(()=>console.log(i),0)
}
// 控制台输入 10 遍 10.
for(var i = 0; i < 10; i++){(function(a){setTimeout(()=>console.log(a),0)
    })(i)
}
 // 控制台输入 0 -9

答案 3: 以后作用域产产生了对父作用域的援用。

答案 4: 不晓得。是跟浏览器的垃圾回收机制无关吗?

开杠了。请问,小伙伴的答案和以上的内容有多少类似水平?

其实,拿着这些问题好好想想,你就会发现这些问题都只是为了最终那一个问题。

闭包的底层实现原理

1. JS 执行上下文

咱们都晓得,咱们手写的 js 代码是要通过浏览器 V8 进行预编译后能力真正的被执行。例如变量晋升、函数晋升。举个栗子。

// 栗子:var d = 'abc';
function a(){console.log("函数 a");
};
console.log(a);   // ƒ a(){ console.log("函数 a"); }
a();              // '函数 a'
var a = "变量 a";  
console.log(a);   // '变量 a'
a();              // a is not a function
var c = 123;

// 输入后果及程序:// ƒ a(){ console.log("函数 a"); }
// '函数 a'
// '变量 a'
// a is not a function

// 栗子预编后相当于:function a(){console.log("函数 a");
};
var d;
console.log(a);  // ƒ a(){ console.log("函数 a"); }
a();              // '函数 a'

a = "变量 a";     // 此时变量 a 赋值,函数申明被笼罩

console.log(a); // "变量 a"
a();         // a is not a function

那么问题来了。请问是谁来执行预编译操作的?那这个谁又是在哪里进行预编译的?

是的,你的纳闷没有错。js 代码运行须要一个运行环境,那这个环境就是 执行上下文。是的,js 运行前的预编译也是在这个环境中进行。

js 执行上下文分三种:

  • 全局执行上下文:代码开始执行时首先进入的环境。
  • 函数执行上下文:函数调用时,会开始执行函数中的代码。
  • eval 执行上下文:不倡议应用,可疏忽。

那么,执行上下文的周期,分为两个阶段:

  • 创立阶段

    • 创立词法环境
    • 生成变量对象 (VO),建设 作用域链 作用域链 作用域链(重要的事说三遍)
    • 确认 this 指向,并绑定this
  • 执行阶段。这个阶段进行变量赋值,函数援用及执行代码。

你当初猜猜看,预编译是产生在什么时候?

噢,我遗记说了,其实与编译还有另一个称说:执行期上下文

预编译产生在函数执行之前。预编译四部曲为:

  1. 创立 AO 对象
  2. 找形参和变量申明,将变量和形参作为 AO 属性名,值为undefined
  3. 将实参和形参相对立
  4. 在函数体里找到函数申明,值赋予函数体。最初程序输入变量值的时候,就是从 AO 对象中拿。

所以,预编译真正的后果是:

var AO = {a = function a(){console.log("函数 a");};
    d = 'abc'
}

咱们从新来。

1. 什么叫变量对象?

变量对象是 js 代码在进入执行上下文时,js 引擎在内存中建设的一个对象,用来寄存以后执行环境中的变量。

2. 变量对象 (VO) 的创立过程

变量对象的创立,是在执行上下文创立阶段,顺次通过以下三个过程:

  • 创立 arguments 对象。

    对于函数执行环境,首先查问是否有传入的实参,如果有,则会将参数名是实参值组成的键值对放入 arguments 对象中。否则,将参数名和 undefined 组成的键值对放入 arguments 对象中。

// 举个栗子 
function bar(a, b, c) {console.log(arguments);  // [1, 2]
    console.log(arguments[2]); // undefined
}
bar(1,2)
  • 当遇到同名的函数时,前面的会笼罩后面的。
console.log(a); // function a() {console.log('Is a ?') }
function a() {console.log('Is a');
}
function a() {console.log('Is a ?')
}

/**ps: 在执行第一行代码之前,函数申明曾经创立实现. 前面的对之前的申明进行了笼罩。**/
  • 查看以后环境中的变量申明并赋值为 undefined。当遇到同名的函数申明, 为了防止函数被赋值为 undefined , 会疏忽此申明
console.log(a); // function a() {console.log('Is a ?') }
console.log(b); // undefined
function a() {console.log('Is a');
}
function a() {console.log('Is a ?');
}
var b = 'Is b';
var a = 10086;

/** 这段代码执行一下,你会发现 a 打印后果仍旧是一个函数,而 b 则是 undefined。**/

依据以上三个步骤,对于变量晋升也就晓得是怎么回事了。

3. 变量对象变为流动对象

执行上下文的第二个阶段,称为执行阶段,在此时,会进行变量赋值,函数援用并执行其余代码,此时,变量对象变为流动对象。

咱们还是举下面的例子:

console.log(a); // function a() {console.log('fjdsfs') }
console.log(b); // undefined
function a() {console.log('Is a');
}
function a() {console.log('Is a?');
}
var b = 'Is b';
console.log(b); // 'Is b'
var a = 10086; 
console.log(a);  // 10086
var b = 'Is b?';
console.log(b); // 'Is b?'

在下面的代码中,代码真正开始执行是从第一行 console.log() 开始的,自这之前,执行上下文是这样的:

// 创立过程
EC= {VO:{}; // 创立变量对象
  scopeChain: {}; // 作用域链}
VO = {argument: {...}; // 以后为全局上下文,所以这个属性值是空的
  a: <a reference> // 函数 a  的援用地址  b: undefiend  // 见上文创立变量对象的第三步}

词法作用域(Lexical scope

这里想阐明,咱们在函数执行上下文中有变量,在全局执行上下文中有变量。JavaScript的一个简单之处在于它如何查找变量,如果在函数执行上下文中找不到变量,它将在调用上下文中寻找它,如果在它的调用上下文中没有找到,就始终往上一级,直到它在全局执行上下文中查找为止。(如果最初找不到,它就是 undefined)。

再来举个栗子:

 1: let top = 0; // 
 2: function createWarp() {3:   function add(a, b) {
 4:     let ret = a + b
 5:     return ret
 6:   }
 7:   return add
 8: }
 9: let sum = createWarp()
10: let result = sum(top, 8)
11: console.log('result:',result)


剖析过程如下:

  • 在全局上下文中申明变量top 并赋值为 0.
  • 2 – 8 行。在全局执行上下文中申明了一个名为 createWarp 的变量,并为其调配了一个函数定义。其中第 3 - 7 行形容了其函数定义,并将函数定义存储到那个变量 (createWarp) 中。
  • 第 9 行。咱们在全局执行上下文中申明了一个名为 sum 的新变量,临时,值为 undefined
  • 第 9 行。遇到 (),表明须要执行或调用一个函数。 那么查找全局执行上下文的内存并查找名为 createWarp 的变量。 显著,曾经在步骤 2 中创立结束。接着,调用它。
  • 调用函数时,回到第 2 行。创立一个新的 createWarp 执行上下文。咱们能够在 createWarp 的执行上下文中创立自有变量。js 引擎createWarp 的上下文增加到调用堆栈(call stack)。因为这个函数没有参数,间接跳到它的主体局部.
  • 3 – 6 行。咱们有一个新的函数申明,createWarp 执行上下文中创立一个变量 addadd 只存在于 createWarp 执行上下文中, 其函数定义存储在名为 add 的自有变量中。
  • 第 7 行,咱们返回变量 add 的内容。js 引擎查找一个名为 add 的变量并找到它. 第 4 行和第 5 行括号之间的内容形成该函数定义。
  • createWarp 调用结束,createWarp 执行上下文将被销毁。add 变量也跟着被销毁。add 函数定义依然存在,因为它返回并赋值给了 sum 变量。(ps: 这才是闭包产生的变量存于内存当中的假相
  • 接下来就是简略的执行过程,不再赘述。。
  • ……
  • 代码执行结束,全局执行上下文被销毁。sum 和 result 也跟着被销毁。

小结一下

当初,如果再让你答复什么是闭包,你能答出多少?

其实,大家说的都对。不论是函数返回一个函数,还是产生了内部作用域的援用,都是有情理的。

所以,什么是闭包?

  • 解释一下作用域链是如何产生的。
  • 解释一下 js 执行上下文的创立、执行过程。
  • 解释一下闭包所产生的变量放在哪了。
  • 最初请把以上 3 点联合起来说给面试官听。
正文完
 0