CSS 选择器有很多,不同的选择器的权重和优先级不一样,对于一个元素,如果存在多个选择器,那么就需要根据权重来计算其优先级。
权重分为四级,分别是:
style="xxx"
,权值为 1000;#content
,权值为 100;.content
、:hover
、[attribute]
,权值为 10;div
、p
,权值为 1。需要注意的是:通用选择器(*)、子选择器(>)和相邻同胞选择器(+)并不在这四个等级中,所以他们的权值都为 0。 权重值大的选择器其优先级也高,相同权重的优先级又遵循后定义覆盖前面定义的情况。
布局的传统解决方案基于盒子模型,依赖 display
属性 + position
属性 + float
属性来实现布局。但是对于那些特殊布局非常不方便,比如,垂直居中就不太容易实现。在目前主流的移动端页面中,使用 flex 布局可以更好地完成需求,因此 flex 布局的知识是必须要掌握的。
基本使用
任何一个容器都可以使用 flex 布局,代码也很简单。
// 按比例分配容器空间
<style type="text/css">
.container {
display: flex;
}
.item {
border: 1px solid #000;
flex: 1;
}
style>
<div class="container">
<div class="item">aaadiv>
<div class="item" style="flex: 2">bbbdiv>
<div class="item">cccdiv>
<div class="item">ddddiv>
div>
可以看到第三个 inline 元素用 像img元素的水平居中也是可以使用的,text-align并不只是对文本元素起作用 block 元素可使用 绝对定位元素可结合 结合margin需要提前知道宽度 也可以使用transfrom,并且不需要知道宽度 inline 元素可设置 绝对定位元素,可结合 绝对定位可结合 绝对定位结合 其他的解决方案还有,不过没必要掌握太多,能说出上文的这几个解决方案即可。 比如还有像flex布局,几行代码就可以实现,也是现在在移动端使用非常多的 如果面试中再遇到这个问题就不用慌了。 所谓“语义”就是为了更易读懂,这要分两部分: 让人更易读懂 对于人来说,代码可读性、语义化就是一个非常广泛的概念了,例如定义 JS 变量的时候使用更易读懂的名称,定义 CSS class 的时候也一样,例如 不过平常考查的“语义化”并不会考查这么广义、这么泛的问题,而是考查 HTML 的语义化,是为了更好地让机器读懂 HTML。 让机器更易读懂 HTML 符合 XML 标准,但又和 XML 不一样 —— HTML 不允许像 XML 那样自定义标签名称,HTML 有自己规定的标签名称。 问题就在这里 —— HTML 为何要自己规定那么多标签名称呢,例如 拿搜索引擎来说,爬虫下载到我们网页的 HTML 代码,它如何更好地去理解网页的内容呢?—— 就是根据 HTML 既定的标签。 为了加强 HTML 语义化,HTML5 标准中又增加了 CSS3 可以实现动画,代替原来的 Flash 和 JavaScript 方案。 如何使用,这里还是带着大家回顾下。 首先,使用 然后,针对一个 CSS 选择器来设置动画,例如针对 一道题:CSS 的transition和animation有何区别? 首先 cookie cookie 本身不是用来做服务器端存储的,它是设计用来在服务器和客户端进行信息传递的,因此我们的每个 HTTP 请求都带着 cookie。但是 cookie 也具备浏览器端存储的能力(例如记住用户名和密码),因此就被开发者用上了。 使用起来也非常简单, 但是 cookie 有它致命的缺点: localStorage 和 sessionStorage HTML5 标准就带来了 这里在使用的时候有个注意点,针对 ECMAScript 中定义了 6 种原始类型: 现在还新增了一个bigInt 类型,这个给大家讲ES6-10语法的时候,给大家讲过。 注意:原始类型是不包含 Object。像Js的数据类型可以分为两大类,值类型,和引用类型。 用于实例和构造函数的对应。例如判断一个变量是否是数组,使用 最佳的判断方法是什么 除了原始类型,ES 还有引用类型,上文提到的 根据 JavaScript 中的变量类型传递方式,又分为值类型和引用类型,值类型变量包括 Boolean、String、Number、Undefined、Null,引用类型包括了 Object 类的所有,如 Date、Array、Function 等。在参数传递方式上,值类型是按值传递,引用类型是按共享传递。 引用类型经常会在代码中按照下面的写法使用,或者说容易不知不觉中造成错误! 虽然 所以也是需要注意一下 JavaScript 是基于原型的语言,原型理解起来非常简单,但却特别重要,下面还是通过题目来理解下JavaScript 的原型概念。 一道题:如何理解 JavaScript 的原型 对于这个问题,可以从下面这几个要点来理解和回答,下面几条必须记住并且理解 通过代码来看一下 先写一个简单的代码示例。 执行 那么如何判断这个属性是不是对象本身的属性呢?使用 题目:如何理解 JS 的原型链 还是接着上面的示例,如果执行 因为 如果在 这样一直往上找,你会发现是一个链式的结构,所以叫做“原型链”。如果一直找到最上层都没有找到,那么就宣告失败,返回 关于原型,原型链的知识几乎是面试中必考的点,所以大家必须要掌握,根据这张经典的图好好理解下。 作用域和闭包是前端面试中,最可能考查的知识点。例如下面的题目: 一道题:现在有个 HTML 片段,要求编写代码,点击编号为几的链接就 一般不知道这个题目用闭包的话,会写出下面的代码: 实际上执行才会发现始终弹出的是 现在使用ES6就更简单了 要理解闭包还跌从执行上下文开始 先讲一个关于 变量提升 的知识点,面试中可能会遇见下面的问题,很多人都回答错误: 一道题:说出下面执行的结果 在一段 JS 脚本(即一个 我们来看下上面的面试小题目,为什么 另外,一个函数在执行之前,也会创建一个 函数执行上下文 环境,跟 全局上下文 差不多,不过 函数执行上下文 中会多出 总结一下: 先搞明白一个很重要的概念 —— 下面再来讲解下什么是作用域和作用域链,作用域链和作用域也是常考的题目。 ES6 之前 JS 没有块级作用域。例如 从上面的例子可以体会到作用域的概念,作用域就是一个独立的地盘,让变量不会外泄、暴露出去。上面的 全局作用域就是最外层的作用域,如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样的坏处就是很容易撞车、冲突。 比如下面这样 这就是为何 jQuery、Zepto 等库的源码,所有的代码都会放在 不过,ES6 中开始加入了块级作用域,使用 大家对于作用域这一块还有没有什么疑问的地方。 首先认识一下什么叫做 自由变量 。如下代码中, 如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是 作用域链 。 讲完这些内容,我们再来看一个例子,通过例子来理解闭包。 自由变量将从作用域链中去寻找,但是 依据的是函数定义时的作用域链,而不是函数执行时,以上这个例子就是闭包。闭包主要有两个应用场景: 现在大家是不是对原型,作用域,原型链,作用链是不是就好理解一些了。 异步和同步也是面试中常考的内容,来看一下。 先看下面的 demo,根据程序阅读起来表达的意思,应该是先打印 再对比以下程序。先打印 这俩到底有何区别?—— 第一个示例中间的步骤根本没有阻塞接下来程序的运行,而第二个示例却阻塞了后面程序的运行。前面这种表现就叫做 异步(后面这个叫做 同步 ),即不会阻塞后面程序的运行。 大家有没有想过,js为什么需要异步? JS 需要异步的根本原因是 JS 是单线程运行的,即在同一时间只能做一件事,不能“一心二用”。 一个 Ajax 请求由于网络比较慢,请求需要 5 秒钟。如果是同步,这 5 秒钟页面就卡死在这里啥也干不了了。异步的话,就好很多了,5 秒等待就等待了,其他事情不耽误做,至于那 5 秒钟等待是网速太慢,不是因为 JS 的原因。 讲到单线程,我们再来看个面试真题: 这是一个很有迷惑性的题目,不少候选人认为 Ajax 代码示例 img 代码示例(常用于打点统计) 了解完前边的一些知识,我们来集中做几道题。 如果用一句话说明 this 的指向,那么即是: 谁调用它,this 就指向谁。但是仅通过这句话,我们很多时候并不能准确判断 this 的指向。因此我们需要借助一些规则去帮助自己: 首先来看一下this绑定的规则,来详细看一下,这样再遇到this的问题,可以从容应对 默认绑定 隐式绑定 需要注意的是:对象属性链中只有最后一层会影响到调用位置。 **隐式绑定有一个大陷阱,**绑定很容易丢失(或者说容易给我们造成误导,我们以为this指向的是什么,但是实际上并非如此). 显示绑定 显式绑定比较好理解,就是通过call,apply,bind的方式,显式的指定this所指向的对象。 call,apply和bind的第一个参数,就是对应函数的this所指向的对象。call和apply的作用一样,只是传参方式不同。call和apply都会执行对应的函数,而bind方法不会。 使用了硬绑定,是不是意味着不会出现隐式绑定所遇到的绑定丢失呢?显然不是这样的,继续往下看。 new 绑定 javaScript和C++不一样,并没有类,在javaScript中,构造函数只是使用new操作符时被调用的函数,这些函数和普通的函数并没有什么不同,它不属于某个类,也不可能实例化出一个类。任何一个函数都可以使用new来调用,因此其实并不存在构造函数,而只有对于函数的“构造调用” 前边我们提到new 操作符都干了什么 因此,我们使用new来调用函数的时候,就会新对象绑定到这个函数的this上。 绑定优先级 绑定例外 凡事都有例外,this的规则也是这样。 如果我们将null或者是undefined作为this的绑定对象传入call、apply或者是bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。 箭头函数 回答这种概念性问题的时候,关键要抓住核心的要点,把要点说全面,然后再稍微加一些解析,要简明扼要,思路清晰,不能拖沓。 面试题目:浏览器从加载到渲染页面的过程 性能优化的题目也是面试常考的,这类题目有很大的扩展性,能够扩展出来很多小细节,而且对个人的技术视野和业务能力有很大的挑战。 优化原则是以更好的用户体验为标准的,目标就是 优化方向 减少页面体积,提升网络加载 优化页面渲染 CSS 放前面,JS 放后面 懒加载(图片懒加载、下拉加载更多) 减少DOM 查询,对 DOM 查询做缓存 减少DOM 操作,多个操作尽量合并在一起执行( 事件节流 尽早执行操作( 使用 SSR 后端渲染,数据直接输出到 HTML 中,减少浏览器使用 JS 模板渲染页面 HTML 的时间 现在算法题,也是经常考的,比如这里大家栏感受下一道简单的算法题。 现在面试,有人经常吐槽 面试造火箭,工作拧螺丝,拿这句话诟病当前一些互联网大厂的算法面试,因此就有这样的言论: 除了应付面试,学算法其实没啥用。 其实这主要原因是因为,现在随着技术生态的发展,各位走在领域前沿的大牛们已经给大家备足了轮子,遇到一般的业务问题直接把人家的方案拿到用就可以了。 但是这样就有一个问题,我们有没有想过自己的价值在哪?比如说照着设计稿画出一个简单的 Button,你能完成,别的前端也能完成,甚至后后端的同学都能把效果差不多做出来,那这个时候谈何个人价值?只不过在一个随时可替代的岗位上完成了大多数人能轻易做到的事情,张三来完成,或者李四来完成,其实没什么区别。 但是现在如果面对的是一个复杂的工程问题,需要你来开发一个辅助业务的脚手架工具,改造框架源码来提高项目的扩展性,或者面对严重的性能问题能马上分析出原因,然后给出解决的思路并在不同因素中平衡,这些都不是一个业余的玩家能够在短时间内胜任的,这就是体现自己价值的地方。 回到算法本身,它代表的是你解决更加复杂问题能力的一部分。 拿大家都基本熟悉的vue为例,如果你以前没有接触过 所以这也是现在很多人,在阅读源码时非常吃力的原因。 既然这样,那我们算法这块就必须掌握, Vue响应式原理、生命周期,组件通信等等。 在 然后会执行 接下来会先执行 接下来是数据更新时会调用的钩子函数 另外还有 最后就是销毁组件的钩子函数 组件通信一般分为以下几种情况: 对于以上每种情况都有多种方式去实现,接下来就来学习下如何实现。 父子通信 父组件通过 这种父子通信方式也就是典型的单向数据流,父组件通过 另外这两种方式还可以使用语法糖 当然我们还可以通过访问 另外如果你使用 Vue 2.3 及以上版本的话还可以使用 兄弟组件通信 对于这种情况可以通过查找父组件中的子组件实现,也就是 跨多层次组件通信 对于这种情况可以使用 Vue 2.2 新增的 API 假设有父组件 A,然后有一个跨多层级的子组件 B 任意组件 这种方式可以通过 Vuex 或者 Event Bus 解决,另外如果你不怕麻烦的话,可以使用这种方式解决上述所有的通信情况 所以一般来说需要依赖别的属性来动态获得值的时候可以使用 另外 如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 对于 并且基于 这道题目其实更多考的是 JS 功底。 组件复用时所有组件实例都会共享 当我们使用 Vue 内部使用了 以上代码简单的实现了如何监听数据的 一般在面试中,比如一面、二面基本会问到具体做过的项目,然后就是追问项目的细节。一般可能会出现这些问题 这类跟项目相关的综合性问题,既能体现候选人的技术水平、业务水平和架构能力,也能够辨别候选人是不是真的做过项目,还能够发现候选人的一些软技能。 按照上面的四段体介绍项目,会让面试官感觉候选人有清晰的思路,对整个项目也有理解和想法,还能够总结反思项目的收益和问题,可谓「一箭三雕」。 对于刚刚找工作的应届生,或者面试官让你进行一个大型项目的设计,候选人可能没有类似的经验。这时候不要用「我不会、没做过」一句话就带过。 如果是实在没有项目可以说,那么可以提自己日常做的练手项目,或者看到一个解决方案的文章/书,提到的某个项目,抒发下自己的想法。 如果是对于面试官提出来需要你设计的项目/系统,可以按照下面几步思考: 总之,切记不要一句「不知道、没做过」就放弃,每一次提问都是自己表现的机会。 介绍项目的过程中,面试官可能会追问技术细节,所以我们在准备面试的时候,应该尽量把技术细节梳理清楚,技术细节包括: 一般来说,做技术选型的时候需要考虑下面几个因素: 在项目中遇见的数据和收益应该做好跟踪,保证数据的真实性和可信性。另外,遇见的坑可能是面试官问得比较多的,尤其现在比较火的一些技术(Vue、React、webpack),一般团队都在使用,所以一定要提前准备下 一般来说,工作中总会遇见一两个自己不喜欢的人,这种情况应该尽量避免冲突,从自己做起慢慢让对方感觉到自己的合作精神。 所以,遇见难打交道的同事,不要急于上报领导,应该自己主动多做一些事情,比如规划好工作安排,让他选择自己做的事情,有了结论记得发邮件确认下来,这样你们的领导和其他成员都会了解到工作的安排,在鞭笞对方的同时,也做到了职责明确。在项目当中,多主动检查项目进展,提前发现逾期的问题。 重点是突出:自己主动沟通解决问题的意识,而不是遇见问题就找领导。 这种情况下,一般通过下面方式来解决: 突出的软技能:分析和解决问题,沟通寻求帮助。 这类问题也是面试官的高频问题,「一个人的业余时间决定了他的未来」,如果回答周末都在追剧打游戏之类的,未免显得太不上进。 一般来说,推荐下面的回答: 周末一般会有三种状态: 这样的回答,既能表现自己阳光善于社交沟通的一面,又能表现自己的上进心。 面试是一个双向选择的事情,所以面试后一般会有提问环节。在提问环节,候选人最好不要什么都不问,更不要只问薪水待遇、是否加班之类的问题。 其实这个时候可以反问面试官了解团队情况、团队做的业务、本职位具体做的工作、工作的规划,甚至一些数据(可能有些问题不会直面回答)。 还可以问一些关于公司培训机会和晋升机会之类的问题。如果是一些高端职位,则可以问一下:自己的 leader 想把这个职位安排给什么样的人,希望多久的时间内可以达到怎样的水平。 面试完了多总结自己哪里做得不好,哪里做得好,都记录下来,后续扬长避短flex: 2
,其他的flex: 1
,这样第二个3.说一下实现居中对齐的方式有哪些
水平居中
text-align: center;
即可,如下:.container {
text-align: center;
}
margin: auto;
,PC端很多都是通过这种方式实现的
.item {
width:400px;
margin: auto;
}
left
和margin
实现,但是必须知道宽度。.item {
width: 300px;
height: 100px;
position: absolute;
left: 50%;
margin-left: -150px;
}
.item {
/* width: 300px; */
height: 100px;
position: absolute;
left: 50%;
margin-left: -50%;
transform: translateX(-50%);
background-color: red;
}
垂直居中
line-height
的值等于height
值,如单行文字垂直居中:.container {
height: 50px;
line-height: 50px;
}
left
和margin
实现,但是必须知道尺寸。
.container {
position: relative;
height: 200px;
}
.item {
width: 80px;
height: 40px;
position: absolute;
left: 50%;
top: 50%;
margin-top: -20px;
margin-left: -40px;
}
transform
实现居中。
.container {
position: relative;
height: 200px;
}
.item {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: blue;
}
margin: auto
,不需要提前知道尺寸,兼容性好。这个也是比较推荐的一种方式.container {
position: relative;
height: 300px;
}
.item {
width: 100px;
height: 50px;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
}
4.说一下对Html语义化的理解
length
list
等,而不是使用a
b
这种谁都看不懂的名称。p
div
h1
ul
等 —— 就是为了语义化。其实,如果你精通 CSS 的话,你完全可以全部用p
h1
ul
等标签可以一个都不用。但是我们不推荐这么做,这样做就失去了 HTML 语义化的意义。
h1
标签就代表是标题;p
里面的就是段落详细内容,权重肯定没有标题高;ul
里面就是列表;strong
就是加粗的强调的内容 …… 如果我们不按照 HTML 语义化来写,全部都用header
section
article
等标签。因此,书写 HTML 时,语义化是非常重要的,否则 W3C 也没必要辛辛苦苦制定出这些标准来。5.CSS3 动画
@keyframes
定义一个动画,名称为testAnimation
,如下代码,通过百分比来设置不同的 CSS 样式,规定动画的变化。所有的动画变化都可以这么定义出来。@keyframes testAnimation
{
0% {background: red; left:0; top:0;}
25% {background: yellow; left:200px; top:0;}
50% {background: blue; left:200px; top:200px;}
75% {background: green; left:0; top:200px;}
100% {background: red; left:0; top:0;}
}
div
元素设置动画,如下:div {
width: 100px;
height: 50px;
position: absolute;
animation-name: myfirst;
animation-duration: 5s;
}
animation-name
对应到动画名称,animation-duration
是动画时长,还有其他属性:
animation-timing-function
:规定动画的速度曲线。默认是ease
animation-delay
:规定动画何时开始。默认是 0animation-iteration-count
:规定动画被播放的次数。默认是 1animation-direction
:规定动画是否在下一周期逆向地播放。默认是normal
animation-play-state
:规定动画是否正在运行或暂停。默认是running
animation-fill-mode
:规定动画执行之前和之后如何给动画的目标应用,默认是none
,保留在最后一帧可以用forwards
transition
和animation
都可以做动效,从语义上来理解,transition
是过渡,由一个状态过渡到另一个状态,比如高度100px
过渡到200px
;而animation
是动画,即更专业做动效的,animation
有帧的概念,可以设置关键帧keyframe
,一个动画可以由多个关键帧多个状态过渡组成,另外animation
也包含上面提到的多个属性。6.存储相关的:cookie 和 localStorage 有何区别?
document.cookie = ....
即可。也有一些第三方库,封装了cookie的一写读取,写入删除的操作,比如js-cookie。我们在后台实战项目中使用的。
sessionStorage
和localStorage
,先拿localStorage
来说,它是专门为了浏览器端缓存而设计的。其优点有:
localStorage.setItem(key, value)
localStorage.getItem(key)
sessionStorage
的区别就在于它是根据 session 过去时间而实现,而localStorage
会永久有效,应用场景不同。例如,一些需要及时失效的重要信息放在sessionStorage
中,一些不重要但是不经常设置的信息,放在localStorage
中。localStorage.setItem
,使用时尽量加入到try-catch
中,某些浏览器是禁用这个 API 的,要注意。Js相关
7.说一下js都有哪些数据类型,如何判断数据的类型,值类型和引用类型有什么区别?
8.如何对js中的数据类型进行判断
typeof
typeof xxx
得到的值有以下几种类型:undefined
boolean
number
string
object
function
、symbol
,比较简单。这里需要注意的有三点:
typeof null
结果是object
,实际这是typeof
的一个bug,null是原始值,非引用类型typeof [1, 2]
结果是object
,结果中没有array
这一项,引用类型除了function
其他的全部都是object
typeof Symbol()
用typeof
获取symbol
类型的值得到的是symbol
,这是 ES6 新增的知识点instanceof
typeof
无法判断,但可以使用[1, 2] instanceof Array
来判断。因为,[1, 2]
是数组,它的构造函数就是Array
。同理:但是instanceof也有问题
比如
[] instanceof Array // true
[] instanceof Object // 也是true
// 使用toString方法
// 对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。
// 而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。借用object的toString方法来进行判断
Object.prototype.toString.call('') ; // [object String]
Object.prototype.toString.call(1) ; // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用
9.值类型和引用类型的区别
typeof
识别出来的类型中,只有object
和function
是引用类型,其他都是值类型。var obj = {
a: 1,
b: [1,2,3]
}
var a = obj.a
var b = obj.b
a = 2
b.push(4)
console.log(obj, a, b)
obj
本身是个引用类型的变量(对象),但是内部的a
和b
一个是值类型一个是引用类型,a
的赋值不会改变obj.a
,但是b
的操作却会反映到obj
对象上。
10.原型和原型链
null
除外)__proto__
属性,属性值是一个普通的对象prototype
属性,属性值也是一个普通的对象__proto__
属性值指向它的构造函数的prototype
属性值
// 要点一:自由扩展属性
var obj = {
}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {
}
fn.a = 100;
// 要点二:__proto__
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);
// 要点三:函数有 prototype
console.log(fn.prototype)
// 要点四:引用类型的 __proto__ 属性值指向它的构造函数的 prototype 属性值
console.log(obj.__proto__ === Object.prototype)
原型的一一些点
// 构造函数
function Foo(name, age) {
this.name = name
}
Foo.prototype.alertName = function () {
alert(this.name)
}
// 创建示例
var f = new Foo('zhangsan')
f.printName = function () {
console.log(this.name)
}
// 测试
f.printName()
f.alertName()
printName
时很好理解,但是执行alertName
时发生了什么?这里再记住一个重点 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__
(即它的构造函数的prototype
)中寻找,因此f.alertName
就会找到Foo.prototype.alertName
。hasOwnProperty
,常用的地方是遍历一个对象的时候。var item
for (item in f) {
// 高级浏览器已经在 for in 中屏蔽了来自原型的属性,但是这里建议大家还是加上这个判断,保证程序的健壮性
if (f.hasOwnProperty(item)) {
console.log(item)
}
}
原型链
f.toString()
时,又发生了什么?// 省略 N 行
// 测试
f.printName()
f.alertName()
f.toString()
f
本身没有toString()
,并且f.__proto__
(即Foo.prototype
)中也没有toString
。这个问题还是得拿出刚才那句话——当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__
(即它的构造函数的prototype
)中寻找。f.__proto__
中没有找到toString
,那么就继续去f.__proto__.__proto__
中寻找,因为f.__proto__
就是一个普通的对象而已嘛!
f.__proto__
即Foo.prototype
,没有找到toString
,继续往上找f.__proto__.__proto__
即Foo.prototype.__proto__
。Foo.prototype
就是一个普通的对象,因此Foo.prototype.__proto__
就是Object.prototype
,在这里可以找到toString
f.toString
最终对应到了Object.prototype.toString
undefined
。最上层是什么 —— Object.prototype.__proto__ === null
11.作用域和闭包
alert
弹出其编号<ul>
<li>编号1,点击我请弹出1li>
<li>2li>
<li>3li>
<li>4li>
<li>5li>
ul>
var list = document.getElementsByTagName('li');
for (var i = 0; i < list.length; i++) {
list[i].addEventListener('click', function(){
alert(i + 1)
}, true)
}
6
,这时候就应该通过闭包来解决:var list = document.getElementsByTagName('li');
for (var i = 0; i < list.length; i++) {
list[i].addEventListener('click', function(i){
return function(){
alert(i + 1)
}
}(i), true)
}
var list = document.getElementsByTagName('li');
for (let i = 0; i < list.length; i++) {
list[i].addEventListener('click', function(){
alert(i + 1)
}, true)
}
12.执行上下文
console.log(a) // undefined
var a = 100
fn('zhangsan') // 'zhangsan' 20
function fn(name) {
age = 20
console.log(name, age)
var age
}
console.log(b); // 这里报错
// Uncaught ReferenceError: b is not defined
b = 100;
标签中)执行之前,要先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个 全局执行上下文 环境,先把代码中即将执行的(内部函数的不算,因为你不知道函数何时执行)变量、函数声明都拿出来。变量先暂时赋值为
undefined
,函数则先声明好可使用。这一步做完了,然后再开始正式执行程序。再次强调,这是在代码执行之前才开始的工作。a
是undefined
,而b
却报错了,实际 JS 在代码执行之前,要「全文解析」,发现var a
,知道有个a
的变量,存入了执行上下文,而b
没有找到var
关键字,这时候没有在执行上下文提前「占位」,所以代码执行的时候,提前报到的a
是有记录的,只不过值暂时还没有赋值,即为undefined
,而b
在执行上下文没有找到,自然会报错(没有找到b
的引用)。this
arguments
和函数的参数。参数和arguments
好理解,这里的this
需要来看一下。
、js 文件或者一个函数
this
,arguments
13.
this
this
的值是在执行的时候才能确认,定义的时候不能确认! 为什么呢 —— 因为this
是执行上下文环境的一部分,而执行上下文需要在代码执行之前确定,而不是定义的时候。看如下例子var a = {
name: 'A',
fn: function () {
console.log(this.name)
}
}
a.fn() // this === a
a.fn.call({
name: 'B'}) // this === {name: 'B'}
var fn1 = a.fn
fn1() // this === window
this
执行会有不同,主要集中在这几个场景中
a.fn()
fn1()
call
apply
bind
,上述代码中a.fn.call({name: 'B'})
一道题:如何理解 JS 的作用域和作用域链
14.作用域
if (true) {
var name = 'zhangsan'
}
console.log(name)
name
就被暴露出去了,因此,JS 没有块级作用域,只有全局作用域和函数作用域。var a = 100
function fn() {
var a = 200
console.log('fn', a) // fn 200
}
console.log('global', a) // global 100
fn()
// 可以看到函数作用域中的a只有在函数中才能访问到
// 张三写的代码中
var data = {
a: 100}
// 李四写的代码中
var data = {
x: true}
(function(){....})()
立即执行函数中。因为放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。这是函数作用域的一个体现。let
定义变量即可,如下:if (true) {
let name = 'zhangsan'
}
console.log(name) // 报错,因为let定义的name是在if这个块级作用域
15.作用域链
console.log(a)
要得到a
变量,但是在当前的作用域中没有定义a
(可对比一下b
)。当前作用域没有定义的变量,这成为 自由变量 。自由变量如何得到 —— 向父级作用域寻找。var a = 100
function fn() {
var b = 200
console.log(a)
console.log(b)
}
fn()
var a = 100
function F1() {
var b = 200
function F2() {
var c = 300
console.log(a) // 自由变量,顺作用域链向父作用域找
console.log(b) // 自由变量,顺作用域链向父作用域找
console.log(c) // 本作用域的变量 不用再向上找
}
F2()
}
F1()
16.闭包
function F1() {
var a = 100
return function () {
console.log(a)
}
}
var f1 = F1()
var a = 200
f1()
function F1() {
var a = 100
return function () {
console.log(a)
}
}
function F2(f1) {
var a = 200
console.log(f1())
}
var f1 = F1()
F2(f1)
// 闭包有个很好的作用,就是可以进行变量的保存
function fn(){
var a= 1;
return function(){
console.log(a);
a++;
};
}
var a = fn();
a();
a();
// 比如普通的
function fn(){
var a= 1;
a++;
console.log(a);
}
fn()
fn();
这个a每次都是从1开始累加
17.异步
同步 vs 异步
100
,1秒钟之后打印200
,最后打印300
。但是实际运行根本不是那么回事。console.log(100)
setTimeout(function () {
console.log(200)
}, 1000)
console.log(300)
// 这就是异步,在js中异步分为宏任务,微任务,宏任务有setTiemout,setInterval,等,微任务,promise
100
,再弹出200
(等待用户确认),最后打印300
。这个运行效果就符合预期要求。console.log(100)
alert(200) // 1秒钟之后点击确认
console.log(300)
异步和单线程
一道题:下面代码的执行过程和结果
var a = true;
setTimeout(function(){
a = false;
}, 100)
while(a){
console.log('while执行了')
}
100ms
之后,由于a
变成了false
,所以while
就中止了,实际不是这样,因为JS是单线程的,所以进入while
循环之后,没有「时间」(线程)去跑定时器了,所以这个代码跑起来是个死循环!前端异步的场景
setTimeout
setInterval
Ajax
加载console.log('start')
$.get('./data1.json', function (data1) {
console.log(data1)
})
console.log('end')
console.log('start')
var img = document.createElement('img')
// 或者 img = new Image()
img.onload = function () {
console.log('loaded')
img.onload = null
}
img.src = '/xxx.png'
console.log('end')
JavaScript面试题
第一道题
// 京程一灯,每日一题
console.log(1 < 2 < 3);
console.log(3 > 2 > 1);
// 写出代码执行结果,并解释为什么
// 答案与解析
true false
对于运算符>、<,一般的计算从左向右
第一个题:1 < 2 等于 true, 然后true < 3,true == 1 ,因此结果是true
第二个题:3 > 2 等于 true, 然后true > 1, true == 1 ,因此结果是false
第二道题
[typeof null, null instanceof Object]
// 写出代码执行的结果,并解释为什么
//答案与解析
["object", false]
1)typeof操作符返回一个字符串,表示未经计算的操作数的类型
类型 结果
Undefined "undefined"
Null "object"
Boolean "boolean"
Number "number"
String "string"
Symbol "symbol"
函数对象 "function"
任何其他对象 "object"
typeof null === 'object';// 从最开始的时候javascript就是这样
JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null的类型标签也成为了 0,typeof null就错误的返回了"object"。这算一个bug,但是被拒绝修复,因为影响的web系统太多
2)instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性
null不是以Object原型创建,因此返回false
第三道题
// 逗号表达式
var x = 20;
var temp = {
x: 40,
foo: function() {
var x = 10;
console.log(this.x);
}
};
(temp.foo, temp.foo)();
// 写出打印结果
20
逗号操作符,逗号操作符会从左到右计算它的操作数,返回最后一个操作数的值。所以(temp.foo, temp.foo)();等价于var fun = temp.foo; fun();,fun调用时this指向window,所以返回20。
第四题
链式调用
// 实现 (5).add(3).minus(2) 功能
// console.log((5).add(3).minus(2)); // 6
// 这里就可以通过原型来实现,这其实就是实现一个链式调用
Number.prototype.add = function (number) {
if (typeof number !== 'number') {
throw new Error('请输入数字~');
}
return this + number;
};
Number.prototype.minus = function (number) {
if (typeof number !== 'number') {
throw new Error('请输入数字~');
}
return this - number;
};
console.log((5).add(3).minus(2));
第五题
var a = 1;
(function a () {
a = 2;
console.log(a);
})();
// 答案
ƒ a () {
a = 2;
console.log(a);
}
这个也是前边讲到过的。
/*
立即调用的函数表达式(IIFE) 有一个 自己独立的 作用域,如果函数名称与内部变量名称冲突,就会永远执行函数本身;所以上面的结果输出是函数本身;
*/
第六题
var a = [0];
if(a){
console.log(a == true);
}else{
console.log(a);
}
/*
答案:false
当a出现在if的条件中时,被转成布尔值,而Boolean([0])为true,所以就进行下一步判断 a == true,在进行比较时,[0]被转换成了0,所以 0==true 为false
js的规则是:
如果比较的是原始类型的值,原始类型的值会转成数值再进行比较
所以 0 == true 就是 0 == Number(true) 0 == 1 => false
'true' == true //false Number('true')->NaN Number(true)->1
'' == 0//true
'1' == true//true Number('1')->1
对象与原始类型值比较,对象会转换成原始类型的值再进行比较。
undefined和null与其它类型进行比较时,结果都为false,他们相互比较时结果为true。(null == undefined)
*/
第七题
var a = ?;
if(a == 1 && a== 2 && a== 3){
console.log(1);
}
/*
比较操作涉及多不同类型的值时候,会涉及到很多隐式转换,其中规则繁多即便是经验老道的程序员也没办法完全记住,特别是用到 `==` 和 `!=` 运算时候。所以一些团队规定禁用 `==` 运算符换用`===` 严格相等。
*/
// 答案一
var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if(aᅠ==1 && a== 2 &&ᅠa==3) {
console.log("1")
}
/*
考察你的找茬能力,注意if里面的空格,它是一个Unicode空格字符,不被ECMA脚本解释为空格字符(这意味着它是标识符的有效字符)。所以它可以解释为
var a_ = 1;
var a = 2;
var _a = 3;
if(a_==1 && a== 2 &&_a==3) {
console.log("1")
}
*/
//答案二
var a = {
i: 1,
toString: function () {
return a.i++;
}
}
if(a == 1 && a == 2 && a == 3) {
console.log('1');
}
/*
如果原始类型的值和对象比较,对象会转为原始类型的值,再进行比较。
对象转换成原始类型的值,算法是先调用valueOf方法;如果返回的还是对象,再接着调用toString方法。
*/
// 答案三
var a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3);
/*
比较巧妙的方式,array也属于对象,
对于数组对象,toString 方法返回一个字符串,该字符串由数组中的每个元素的 toString() 返回值经调用 join() 方法连接(由逗号隔开)组成。
数组 toString 会调用本身的 join 方法,这里把自己的join方法该写为shift,每次返回第一个元素,而且原数组删除第一个值,正好可以使判断成立
*/
// 答案四
var i = 0;
with({
get a() {
return ++i;
}
}) {
if (a == 1 && a == 2 && a == 3)
console.log("1");
}
/*
with 也是被严重建议不使用的对象,这里也是利用它的特性在代码块里面利用对象的 get 方法动态返回 i.
*/
// 答案五
var val = 0;
Object.defineProperty(window, 'a', {
get: function() {
return ++val;
}
});
if (a == 1 && a == 2 && a == 3) {
console.log('1');
}
/*
全局变量也相当于 window 对象上的一个属性,这里用defineProperty 定义了 a的 get 也使得其动态返回值。和with 有一些类似。
*/
// 答案六
let a = {
[Symbol.toPrimitive]: ((i) => () => ++i) (0)};
if (a == 1 && a == 2 && a == 3) {
console.log('1');
}
/*
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。我们之前在定义类的内部私有属性时候习惯用 __xxx ,这种命名方式避免别人定义相同的属性名覆盖原来的属性,有了 Symbol 之后我们完全可以用 Symbol值来代替这种方法,而且完全不用担心被覆盖。
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。Symbol.toPrimitive就是其中一个,它指向一个方法,表示该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。这里就是改变这个属性,把它的值改为一个 闭包 返回的函数。
*/
业务中一般不会写出这种代码,重点还是知识点的考察
第八题
let a = {
n: 1};
let b = a;
a.x = a = {
n: 2};
console.log(a.x)
console.log(b.x)
答案:
undefined {
n:2}
注意点:
1: 点的优先级大于等号的优先级
2: 对象以指针的形式进行存储,每个新对象都是一份新的存储地址
解析:
- `var b = a;` b 和 a 都指向同一个地址。
- `.`的优先级高于`=`。所以先执行`a.x`,于是现在的`a`和`b`都是`{n: 1, x: undefined}`。
- `=`是从右向左执行。所以是执行 `a = {n: 2}`,于是`a`指向了`{n: 2}`
- 再执行 `a.x = a`。 这里注意,`a.x` 是最开始执行的,已经是`{n: 1, x: undefined}`这个地址了,而不是一开的的那个`a`,所以也就不是`{n: 2}`了。而且`b`和旧的`a`是指向一个地址的,所以`b`也改变了。
- 但是,`=`右面的a,是已经指向了新地址的新`a`。
- 所以,`a.x = a` 可以看成是`{n: 1, x: undefined}.x = {n: 2}`
- 最终得出
a = {
n: 2 },
b = {
n: 1,
x: {
n: 2 }
}
第九题
// 实现一个模板引擎
let template = '我是{
{name}},年龄{
{age}},性别{
{sex}}';
let data = {
name: '姓名',
age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined
function render(template,data){
// your code
}
// 补充代码,使代码可以正确执行
// 代码实现
function render(template, data) {
const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
if (reg.test(template)) {
// 判断模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
return render(template, data); // 递归的渲染并返回渲染后的结构
}
return template; // 如果模板没有模板字符串直接返回
}
第十题 this指向
// 来一道面试题
var a=10;
var foo={
a:20,
bar:function(){
var a=30;
return this.a;
}
}
console.log(foo.bar());
console.log((foo.bar)());
console.log((foo.bar=foo.bar)());
console.log((foo.bar,foo.bar)());
// 答案:
20 20 10 10
// 第一问 foo.bar()
/*
foo调用,this指向foo , 此时的 this 指的是foo,输出20
*/
// 第二问 (foo.bar)()
/*
给表达式加了括号,而括号的作用是改变表达式的运算顺序,而在这里加与不加括号并无影响;相当于foo.bar(),输出20
*/
// 第三问 (foo.bar=foo.bar)()
/*
等号运算,
相当于重新给foo.bar定义,即
foo.bar = function () {
var a = 10;
return this.a;
}
就是普通的复制,一个匿名函数赋值给一个全局变量
所以这个时候foo.bar是在window作用域下而不是foo = {}这一块级作用域,所以这里的this指代的是window,输出10
*/
// 第四问 (foo.bar,foo.bar)()
/*
1.逗号运算符,
2.逗号表达式,求解过程是:先计算表达式1的值,再计算表达式2的值,……一直计算到表达式n的值。最后整个逗号表达式的值是表达式n的值。逗号运算符的返回值是最后一个表达式的值。
3.其实这里主要还是经过逗号运算符后,就是纯粹的函数了,不是对象方法的引用,所以这里this指向的是window,输出10
4.第三问,第四问,一个是等号运算,一个是逗号运算,可以这么理解,经过赋值,运算符运算后,都是纯粹的函数,不是对象方法的引用。所以函数指向的this都是windows的。
*/
// 默认绑定,在不能应用其它绑定规则时使用的默认规则,通常是独立函数调用。
function sayHi(){
console.log('Hello,', this.name);
}
var name = 'yideng';
sayHi();
//在调用Hi()时,应用了默认绑定,this指向全局对象(非严格模式下),严格模式下,this指向undefined,undefined上没有this对象,会抛出错误。
// 如果在浏览器环境中运行,那么结果就是 Hello,yideng
// 如果在node环境中运行,结果就是 Hello,undefined.这是因为node中name并不是挂在全局对象上的。
// 函数的调用是在某个对象上触发的,即调用位置上存在上下文对象。典型的形式为 XXX.fun()
function sayHi(){
console.log('Hello,', this.name);
}
var person = {
name: 'yidneg1',
sayHi: sayHi
}
var name = 'yidneg2';
person.sayHi();
// Hello yideng1
// sayHi函数声明在外部,严格来说并不属于person,但是在调用sayHi时,调用位置会使用person的上下文来引用函数,隐式绑定会把函数调用中的this(即此例sayHi函数中的this)绑定到这个上下文对象(即此例中的person)
function sayHi(){
console.log('Hello,', this.name);
}
var person2 = {
name: 'yideng1',
sayHi: sayHi
}
var person1 = {
name: 'yideng2',
friend: person2
}
person1.friend.sayHi();
// Hello yideng1
//因为只有最后一层会确定this指向的是什么,不管有多少层,在判断this的时候,我们只关注最后一层,即此处的friend。
function sayHi(){
console.log('Hello,', this.name);
}
var person = {
name: 'yideng1',
sayHi: sayHi
}
var name = 'yideng2';
var Hi = person.sayHi;
Hi();
// Htllo yideng2
// Hi直接指向了sayHi的引用,在调用的时候,跟person没有半毛钱的关系,针对此类问题,我建议大家只需牢牢记住这个格式:XXX.fn();fn()前如果什么都没有,那么肯定不是隐式绑定。
function sayHi(){
console.log('Hello,', this.name);
}
var person = {
name: 'yideng1',
sayHi: sayHi
}
var name = 'yideng2';
var Hi = person.sayHi;
Hi.call(person); //Hi.apply(person)
// Hello yideng1 因为使用硬绑定明确将this绑定在了person上。
function sayHi(){
console.log('Hello,', this.name);
}
var person = {
name: 'yideng1',
sayHi: sayHi
}
var name = 'yideng2';
var Hi = function(fn) {
fn();
}
Hi.call(person, person.sayHi);
// Hello yideng2
输出的结果是 Hello, Wiliam. 原因很简单,Hi.call(person, person.sayHi)的确是将this绑定到Hi中的this了。但是在执行fn的时候,相当于直接调用了sayHi方法(记住: person.sayHi已经被赋值给fn了,隐式绑定也丢了),没有指定this的值,对应的是默认绑定。
现在,我们希望绑定不会丢失,要怎么做?很简单,调用fn的时候,也给它硬绑定。
var Hi = function(fn) {
fn.call(this);
}
这样就行了
因为person被绑定到Hi函数中的this上,fn又将这个对象绑定给了sayHi的函数。这时,sayHi中的this指向的就是person对象。
function sayHi(name){
this.name = name;
}
var Hi = new sayHi('yideng');
console.log('Hello,', Hi.name); // Hello yideng
var foo = {
name: 'yideng1'
}
var name = 'yideng2';
function bar() {
console.log(this.name);
}
bar.call(null); //yideng2
因为这时实际应用的是默认绑定规则。
第十一题 加载页面渲染过程
时,会执行并阻塞渲染
和
这种外链加载 CSS 和 JS 的标签,浏览器会异步下载,下载过程和上文中下载 HTML 的流程一样。只不过,这里下载下来的字符串是 CSS 或者 JS 格式的。
就停止渲染,执行 JS 代码。因为浏览器渲染和 JS 执行共用一个线程,而且这里必须是单线程操作,多线程会产生渲染 DOM 冲突。待
内容执行完之后,浏览器继续渲染。最后再思考一个问题 —— 为何要将 JS 放在 HTML 底部?—— JS 放在底部可以保证让浏览器优先渲染完现有的 HTML 内容,让用户先看到内容,体验好。另外,JS 执行如果涉及 DOM 操作,得等待 DOM 解析完成才行,JS 放在底部执行时,HTML 肯定都解析成了 DOM 结构。JS 如果放在 HTML 顶部,JS 执行的时候 HTML 还没来得及转换为 DOM 结构,可能会报错。
第十二题 性能优化相关
DocumentFragment
)DOMContentLoaded
)
window.addEventListener('load', function () {
// 页面的全部资源加载完才会执行,包括图片、视频等
})
document.addEventListener('DOMContentLoaded', function () {
// DOM 渲染完即可执行,此时图片、视频还可能没有加载完
})
第十三题 算法题
/*
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效
有效字符串需满⾜:
1. 左括号必须⽤相同类型的右括号闭合。
2. 左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例1:
输⼊: "()"
输出: true
示例2:
输⼊: "()[]{}"
输出: true
示例 3:
输⼊: "(]"
输出: false
示例 4:
输⼊: "([)]"
输出: false
示例 5:
输⼊: "{[]}"
输出: true
*/
// 思路
出栈、入栈的思想
1)首先,我们通过上边的例子可以分析出什么样子括号匹配是复合物条件的,两种情况。
①第一种(非嵌套情况):{
} [] ;
②第二种(嵌套情况):{
[ ( ) ] } 。
除去这两种情况都不是符合条件的。
2)然后,我们将这些括号自右向左看做栈结构,右侧是栈顶,左侧是栈尾。
3)如果编译器中的括号是左括号,我们就入栈(左括号不用检查匹配);如果是右括号,就取出栈顶元素检查是否匹配。
4)如果匹配,就出栈。否则,就返回 false;
// 代码实现
var isValid = function(s){
let stack = [];
var obj = {
"[": "]",
"{": "}",
"(": ")",
};
// 取出字符串中的括号
for (var i = 0; i < s.length;i++){
if(s[i] === "[" || s[i] === "{" || s[i] === "("){
// 如果是左括号,就进栈
stack.push(s[i]);
}else{
var key = stack.pop();
// 如果栈顶元素不相同,就返回false
if(obj[key] !== s[i]){
return false;
}
}
}
return stack.length === 0
}
var str = "([{}])"
isValid(str); // true
深度优先遍历
和递归
的概念,没有看过相应的代码,那么虚拟 DOM 整个patch
的源码你是基本不可能看懂的;如果你没有系统掌握过栈
先进后出这种特点的应用,你也是很难理解 Vue 模板编译阶段为什么要用栈来检查标签是否正常闭合;同样的,如果你没有回溯
这种算法的代码经验,你也是很难理解 Vue 模板编译的优化阶段,到底是怎样在从父到子深度优先遍历的过程中检查到非静态的子节点后给父节点打上标记;并且,如果你以前不知道 LRU 缓存淘汰算法
究竟是个什么东西,你看到keep-alive
组件的实现这里会非常纳闷:第十四题 Vue相关
生命周期钩子函数
beforeCreate
钩子函数调用的时候,是获取不到 props
或者 data
中的数据的,因为这些数据的初始化都在 initState
中。created
钩子函数,在这一步的时候已经可以访问到之前不能访问到的数据,但是这时候组件还没被挂载,所以是看不到的。beforeMount
钩子函数,开始创建 VDOM,最后执行 mounted
钩子,并将 VDOM 渲染为真实 DOM 并且渲染数据。组件中如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。beforeUpdate
和 updated
,这两个钩子函数没什么好说的,就是分别在数据更新前和更新后会调用。keep-alive
独有的生命周期,分别为 activated
和 deactivated
。用 keep-alive
包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated
钩子函数,命中缓存渲染后会执行 actived
钩子函数。beforeDestroy
和 destroyed
。前者适合移除事件、定时器等等,否则可能会引起内存泄露的问题。然后进行一系列的销毁操作,如果有子组件的话,也会递归销毁子组件,所有子组件都销毁完毕后才会执行根组件的 destroyed
钩子函数。组件通信
props
传递数据给子组件,子组件通过 emit
发送事件传递数据给父组件,这两种方式是最常用的父子通信实现办法。props
传递数据,子组件不能直接修改 props
, 而是必须通过发送事件的方式告知父组件修改数据。v-model
来直接实现,因为 v-model
默认会解析成名为 value
的 prop
和名为 input
的事件。这种语法糖的方式是典型的双向绑定,常用于 UI 控件上,但是究其根本,还是通过事件的方法让父组件修改数据。$parent
或者 $children
对象来访问组件实例中的方法和数据。$listeners
和 .sync
这两个属性。$listeners
属性会将父组件中的 (不含 .native
修饰器的) v-on
事件监听器传递给子组件,子组件可以通过访问 $listeners
来自定义监听器。.sync
属性是个语法糖,可以很简单的实现子组件与父组件通信
<input :value.sync="value" />
<input :value="value" @update:value="v => value = v">comp>
<script>
this.$emit('update:value', 1)
script>
this.$parent.$children
,在 $children
中可以通过组件 name
查询到需要的组件实例,然后进行通信。provide / inject
,虽然文档中不推荐直接使用在业务中,但是如果用得好的话还是很有用的。// 父组件 A
export default {
provide: {
data: 1
}
}
// 子组件 B
export default {
inject: ['data'],
mounted() {
// 无论跨几层都能获得父组件的 data 属性
console.log(this.data) // => 1
}
}
computed 和 watch 区别
computed
是计算属性,依赖其他属性计算值,并且 computed
的值有缓存,只有当计算值变化才会返回内容。watch
监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。computed
,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用 watch
。computed
和 watch
还都支持对象的写法,这种方式知道的人并不多。vm.$watch('obj', {
// 深度遍历
deep: true,
// 立即触发
immediate: true,
// 执行的函数
handler: function(val, oldVal) {
}
})
var vm = new Vue({
data: {
a: 1 },
computed: {
aPlus: {
// this.aPlus 时触发
get: function () {
return this.a + 1
},
// this.aPlus = 1 时触发
set: function (v) {
this.a = v - 1
}
}
}
})
keep-alive 组件有什么作用
keep-alive
组件包裹需要保存的组件。keep-alive
组件来说,它拥有两个独有的生命周期钩子函数,分别为 activated
和 deactivated
。用 keep-alive
包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated
钩子函数,命中缓存渲染后会执行 actived
钩子函数。v-show 与 v-if 区别
v-show
只是在 display: none
和 display: block
之间切换。无论初始条件是什么都会被渲染出来,后面只需要切换 CSS,DOM 还是一直保留着的。所以总的来说 v-show
在初始渲染时有更高的开销,但是切换开销很小,更适合于频繁切换的场景。v-if
的话就得说到 Vue 底层的编译了。当属性初始为 false
时,组件就不会被渲染,直到条件为 true
,并且切换条件时会触发销毁/挂载组件,所以总的来说在切换时开销更高,更适合不经常切换的场景。v-if
的这种惰性渲染机制,可以在必要的时候才去渲染组件,减少整个页面的初始渲染开销。组件中 data 什么时候可以使用对象
data
,如果 data
是对象的话,就会造成一个组件修改 data
以后会影响到其他所有组件,所以需要将 data
写成函数,每次用到就调用一次函数获得新的数据。new Vue()
的方式的时候,无论我们将 data
设置为对象还是函数都是可以的,因为 new Vue()
的方式是生成一个根组件,该组件不会复用,也就不存在共享 data
的情况了。响应式原理
Object.defineProperty()
来实现数据响应式,通过这个函数可以监听到 set
和 get
的事件。var data = {
name: 'yck' }
observe(data)
let name = data.name // -> get value
data.name = 'yyy' // -> change value
function observe(obj) {
// 判断类型
if (!obj || typeof obj !== 'object') {
return
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
function defineReactive(obj, key, val) {
// 递归子属性
observe(val)
Object.defineProperty(obj, key, {
// 可枚举
enumerable: true,
// 可配置
configurable: true,
// 自定义函数
get: function reactiveGetter() {
console.log('get value')
return val
},
set: function reactiveSetter(newVal) {
console.log('change value')
val = newVal
}
})
}
set
和 get
的事件,第十五题 聊到具体的项目
如何进行应对
没有做过大型项目怎么办
项目细节和技术点的追问
第十六题:软技能问题回答
回想下你遇见过最难打交道的同事,你是如何跟他沟通的
当你被分配一个几乎不可能完成的任务时,你会怎么做
业余时间都做什么?除了写码之外还有什么爱好
面试注意事项
提问环节
面试后的总结和思考