❤️ 谢谢支持
喜欢的话别忘了 关注、点赞哦~。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
.item {
width:400px;
margin: auto;
}
绝对定位元素可结合left
和margin
实现,但是必须知道宽度。
结合margin需要提前知道宽度
.item {
width: 300px;
height: 100px;
position: absolute;
left: 50%;
margin-left: -150px;
}
也可以使用transfrom,并且不需要知道宽度
.item {
/\* width: 300px; \*/
height: 100px;
position: absolute;
left: 50%;
margin-left: -50%;
transform: translateX(-50%);
background-color: red;
}
垂直居中
inline 元素可设置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
实现居中。
- 优点:不需要提前知道尺寸
- 缺点:兼容性不好 transfrom ,使用在浏览器中使用需要添加前缀进行处理
.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;
}
其他的解决方案还有,不过没必要掌握太多,能说出上文的这几个解决方案即可。
比如还有像flex布局,几行代码就可以实现,也是现在在移动端使用非常多的
如果面试中再遇到这个问题就不用慌了。
4.说一下对Html语义化的理解
所谓“语义”就是为了更易读懂,这要分两部分:
- 让人(写程序、读程序)更易读懂
- 让机器(浏览器、搜索引擎)更易读懂
让人更易读懂
对于人来说,代码可读性、语义化就是一个非常广泛的概念了,例如定义 JS 变量的时候使用更易读懂的名称,定义 CSS class 的时候也一样,例如length
list
等,而不是使用a
b
这种谁都看不懂的名称。
不过平常考查的“语义化”并不会考查这么广义、这么泛的问题,而是考查 HTML 的语义化,是为了更好地让机器读懂 HTML。
让机器更易读懂
HTML 符合 XML 标准,但又和 XML 不一样 —— HTML 不允许像 XML 那样自定义标签名称,HTML 有自己规定的标签名称。
问题就在这里 —— HTML 为何要自己规定那么多标签名称呢,例如p
div
h1
ul
等 —— 就是为了语义化。其实,如果你精通 CSS 的话,你完全可以全部用<div>
标签来实现所有的网页效果,其他的p
h1
ul
等标签可以一个都不用。但是我们不推荐这么做,这样做就失去了 HTML 语义化的意义。
拿搜索引擎来说,爬虫下载到我们网页的 HTML 代码,它如何更好地去理解网页的内容呢?—— 就是根据 HTML 既定的标签。h1
标签就代表是标题;p
里面的就是段落详细内容,权重肯定没有标题高;ul
里面就是列表;strong
就是加粗的强调的内容 …… 如果我们不按照 HTML 语义化来写,全部都用<div>
标签,那搜索引擎将很难理解我们网页的内容。
为了加强 HTML 语义化,HTML5 标准中又增加了header
section
article
等标签。因此,书写 HTML 时,语义化是非常重要的,否则 W3C 也没必要辛辛苦苦制定出这些标准来。
5.CSS3 动画
CSS3 可以实现动画,代替原来的 Flash 和 JavaScript 方案。
如何使用,这里还是带着大家回顾下。
首先,使用@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;}
}
然后,针对一个 CSS 选择器来设置动画,例如针对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
一道题:CSS 的transition和animation有何区别?
首先transition
和animation
都可以做动效,从语义上来理解,transition
是过渡,由一个状态过渡到另一个状态,比如高度100px
过渡到200px
;而animation
是动画,即更专业做动效的,animation
有帧的概念,可以设置关键帧keyframe
,一个动画可以由多个关键帧多个状态过渡组成,另外animation
也包含上面提到的多个属性。
6.存储相关的:cookie 和 localStorage 有何区别?
cookie
cookie 本身不是用来做服务器端存储的,它是设计用来在服务器和客户端进行信息传递的,因此我们的每个 HTTP 请求都带着 cookie。但是 cookie 也具备浏览器端存储的能力(例如记住用户名和密码),因此就被开发者用上了。
使用起来也非常简单,document.cookie = ....
即可。也有一些第三方库,封装了cookie的一写读取,写入删除的操作,比如js-cookie。我们在后台实战项目中使用的。
但是 cookie 有它致命的缺点:
- 存储量太小,只有 4KB
- 所有 HTTP 请求都带着,会影响获取资源的效率
- API 简单,需要封装才能用
localStorage 和 sessionStorage
HTML5 标准就带来了sessionStorage
和localStorage
,先拿localStorage
来说,它是专门为了浏览器端缓存而设计的。其优点有:
- 存储量增大到 5MB
- 不会带到 HTTP 请求中
- API 适用于数据存储
localStorage.setItem(key, value)
localStorage.getItem(key)
sessionStorage
的区别就在于它是根据 session 过去时间而实现,而localStorage
会永久有效,应用场景不同。例如,一些需要及时失效的重要信息放在sessionStorage
中,一些不重要但是不经常设置的信息,放在localStorage
中。
这里在使用的时候有个注意点,针对localStorage.setItem
,使用时尽量加入到try-catch
中,某些浏览器是禁用这个 API 的,要注意。
Js相关
7.说一下js都有哪些数据类型,如何判断数据的类型,值类型和引用类型有什么区别?
ECMAScript 中定义了 6 种原始类型:
- Boolean
- String
- Number
- Null
- Undefined
- Symbol(ES6 新定义)
现在还新增了一个bigInt 类型,这个给大家讲ES6-10语法的时候,给大家讲过。
注意:原始类型是不包含 Object。像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.值类型和引用类型的区别
除了原始类型,ES 还有引用类型,上文提到的typeof
识别出来的类型中,只有object
和function
是引用类型,其他都是值类型。
根据 JavaScript 中的变量类型传递方式,又分为值类型和引用类型,值类型变量包括 Boolean、String、Number、Undefined、Null,引用类型包括了 Object 类的所有,如 Date、Array、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.原型和原型链
JavaScript 是基于原型的语言,原型理解起来非常简单,但却特别重要,下面还是通过题目来理解下JavaScript 的原型概念。
一道题:如何理解 JavaScript 的原型
对于这个问题,可以从下面这几个要点来理解和回答,下面几条必须记住并且理解
- 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(
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)
}
}
题目:如何理解 JS 的原型链
原型链
还是接着上面的示例,如果执行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.作用域和闭包
作用域和闭包是前端面试中,最可能考查的知识点。例如下面的题目:
一道题:现在有个 HTML 片段,要求编写代码,点击编号为几的链接就alert
弹出其编号
<ul>
<li>编号1,点击我请弹出1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</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)
}
现在使用ES6就更简单了
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 脚本(即一个<script>
标签中)执行之前,要先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个 全局执行上下文 环境,先把代码中即将执行的(内部函数的不算,因为你不知道函数何时执行)变量、函数声明都拿出来。变量先暂时赋值为undefined
,函数则先声明好可使用。这一步做完了,然后再开始正式执行程序。再次强调,这是在代码执行之前才开始的工作。
我们来看下上面的面试小题目,为什么a
是undefined
,而b
却报错了,实际 JS 在代码执行之前,要「全文解析」,发现var a
,知道有个a
的变量,存入了执行上下文,而b
没有找到var
关键字,这时候没有在执行上下文提前「占位」,所以代码执行的时候,提前报到的a
是有记录的,只不过值暂时还没有赋值,即为undefined
,而b
在执行上下文没有找到,自然会报错(没有找到b
的引用)。
另外,一个函数在执行之前,也会创建一个 函数执行上下文 环境,跟 全局上下文 差不多,不过 函数执行上下文 中会多出this
arguments
和函数的参数。参数和arguments
好理解,这里的this
需要来看一下。
总结一下:
- 范围:一段
<script>
、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.作用域
ES6 之前 JS 没有块级作用域。例如
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只有在函数中才能访问到
全局作用域就是最外层的作用域,如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样的坏处就是很容易撞车、冲突。
比如下面这样
// 张三写的代码中
var data = {a: 100}
// 李四写的代码中
var data = {x: true}
这就是为何 jQuery、Zepto 等库的源码,所有的代码都会放在(function(){....})()
立即执行函数中。因为放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。这是函数作用域的一个体现。
不过,ES6 中开始加入了块级作用域,使用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 异步
先看下面的 demo,根据程序阅读起来表达的意思,应该是先打印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)
这俩到底有何区别?—— 第一个示例中间的步骤根本没有阻塞接下来程序的运行,而第二个示例却阻塞了后面程序的运行。前面这种表现就叫做 异步(后面这个叫做 同步 ),即不会阻塞后面程序的运行。
异步和单线程
大家有没有想过,js为什么需要异步?
JS 需要异步的根本原因是 JS 是单线程运行的,即在同一时间只能做一件事,不能“一心二用”。
一个 Ajax 请求由于网络比较慢,请求需要 5 秒钟。如果是同步,这 5 秒钟页面就卡死在这里啥也干不了了。异步的话,就好很多了,5 秒等待就等待了,其他事情不耽误做,至于那 5 秒钟等待是网速太慢,不是因为 JS 的原因。
讲到单线程,我们再来看个面试真题:
一道题:下面代码的执行过程和结果
var a = true;
setTimeout(function(){
a = false;
}, 100)
while(a){
console.log('while执行了')
}
这是一个很有迷惑性的题目,不少候选人认为100ms
之后,由于a
变成了false
,所以while
就中止了,实际不是这样,因为JS是单线程的,所以进入while
循环之后,没有「时间」(线程)去跑定时器了,所以这个代码跑起来是个死循环!
前端异步的场景
- 定时
setTimeout
setInterval
- 网络请求,如
Ajax
<img>
加载
Ajax 代码示例
console.log('start')
$.get('./data1.json', function (data1) {
console.log(data1)
})
console.log('end')
img 代码示例(常用于打点统计)
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的。
\*/
如果用一句话说明 this 的指向,那么即是: 谁调用它,this 就指向谁。但是仅通过这句话,我们很多时候并不能准确判断 this 的指向。因此我们需要借助一些规则去帮助自己:
首先来看一下this绑定的规则,来详细看一下,这样再遇到this的问题,可以从容应对
-
默认绑定
// 默认绑定,在不能应用其它绑定规则时使用的默认规则,通常是独立函数调用。
function sayHi(){
console.log(‘Hello,’, this.name);
}
var name = ‘yideng’;
sayHi();
//在调用Hi()时,应用了默认绑定,this指向全局对象(非严格模式下),严格模式下,this指向undefined,undefined上没有this对象,会抛出错误。
// 如果在浏览器环境中运行,那么结果就是 Hello,yideng
// 如果在node环境中运行,结果就是 Hello,undefined.这是因为node中name并不是挂在全局对象上的。 -
-
隐式绑定
总结一下
面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。
还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。
为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。
前端面试题汇总
JavaScript
性能
linux
前端资料汇总
前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。
ayHi(){
console.log(‘Hello,’, this.name);
}
var name = ‘yideng’;
sayHi();
//在调用Hi()时,应用了默认绑定,this指向全局对象(非严格模式下),严格模式下,this指向undefined,undefined上没有this对象,会抛出错误。
// 如果在浏览器环境中运行,那么结果就是 Hello,yideng
// 如果在node环境中运行,结果就是 Hello,undefined.这是因为node中name并不是挂在全局对象上的。
```
- 隐式绑定
总结一下
面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。
还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。
为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。
前端面试题汇总
JavaScript
性能
linux
前端资料汇总
前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。