中级前端笔试面试题总结

这篇博客总结了前端面试中常见的知识点,包括变量声明提升、this指针、运算符优先级、浏览器的localStorage、sessionStorage与cookie的区别,以及JavaScript的事件循环机制。还提到了Promise的使用和异步操作,以及ES6中let与var的区别。文章旨在帮助准备面试的前端开发者巩固基础知识。
摘要由CSDN通过智能技术生成

在网上找到一篇文章,里面有一道面试题,考察了包括变量定义提升、this指针指向、运算符优先级、原型、继承、全局变量污染、对象属性及原型属性优先级等许多知识点。
而就其中声明提前相关的知识,我觉得也十分有参考价值:

function Foo() {
    getName = function () { alert (1); };
    return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

// 请写出以下输出结果:
Foo.getName();
getName(); // 声明提前
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

这道题的答案是:2、4、1、1、2、3、3。

这里考察声明提前的题目在代码中已经标出,这里声明getName方法的两个语句:

var getName = function () { alert (4) };
function getName() { alert (5) }

实际上在解析的时候是这样的顺序:

function getName() { alert (5) }
var getName;
getName = function () { alert (4) };

如果我们在代码中间再加两个断点:
getName(); // 5
var getName = function () { alert (4) };
getName(); // 4
function getName() { alert (5) }

在第一次getName时,function的声明和var的声明都被提前到了第一次getName的前面,而getName的赋值操作并不会提前,单纯使用var的声明也不会覆盖function所定义的变量,因此第一次getName输出的是function声明的5; 而第二次getName则是发生在赋值语句的后面,因此输出的结果是4,所以实际代码的执行顺序是这样:

function getName() { alert (5) }
var getName;
getName(); // 5
getName = function () { alert (4) };
getName(); // 4

2、浏览器存储

localStorage,sessionStorage和cookie的区别

共同点:都是保存在浏览器端、仅同源可用的存储方式
1.数据存储方面
•cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下
•sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。
2.存储数据大小
•存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。
•sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
3.数据存储有效期
•sessionStorage:仅在当前浏览器窗口关闭之前有效;
•localStorage:始终有效,窗口或浏览器关闭也一直保存,本地存储,因此用作持久数据;
•cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
4.作用域不同
•sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;
•localstorage在所有同源窗口中都是共享的;也就是说只要浏览器不关闭,数据仍然存在
•cookie: 也是在所有同源窗口中都是共享的.也就是说只要浏览器不关闭,数据仍然存在

3、跨域

不久我写了一个帖子,对同源策略及各种跨域的方式进行了总结: 什么是跨域,为什么浏览器会禁止跨域,及其引起的发散性学习

4、Promise的使用及原理

Promise是ES6加入的新特性,用于更合理的解决异步编程问题,关于用法阮一峰老师在ECMAScript 6 入门中作出了详细的说明,在此就不重复了。

30分钟,让你彻底明白Promise原理

上面这篇文章则是对Promise的原理进行的详细的说明,在这里,我提取最简单的Promise实现方式来对Promise的原理进行说明:

function Promise(fn) {
    var value = null,
        callbacks = [];  // callbacks为数组,因为可能同时有很多个回调

    this.then = function (onFulfilled) {
        callbacks.push(onFulfilled);
    };

    function resolve(value) {
        callbacks.forEach(function (callback) {
            callback(value);
        });
    }

    fn(resolve);
}

首先, then 里面声明的单个或多个函数,将被推入 callbacks 列表,在Promise实例调用 resolve 方法时遍历调用,并传入 resolve 方法中传入的参数值。

以下,使用一个简单的例子来对Promise的执行流程进行分析:
fu

nctionm func () {
	return new Promise(function (resolve) {
		setTimeout(function () {
			resolve('complete')
		}, 3000);
	})
}

func().then(function (res) {
	console.log(res); // complete
})

func 函数的定义是返回了一个Promise实例,声明实例时传入的回调函数加入了一个 resolve 参数(这个 resolve 参数在Promise中的 fn(resolve) 定义中获取 resolve 的函数实体),回调中执行了一个异步操作,在异步操作完成的回调中执行了 resolve 函数。

再看执行步骤, func 函数返回了一个Promise实例,实例则可以执行Promise构造函数中定义的 then 方法, then 方法中传入的回调则会在 resolve (即异步操作完成后)执行,由此实现了通过 then 方法执行异步操作完成后回调的功能。

5、JavaScript事件循环机制

原文中贴出的文章具有很大参考价值,先贴个链接: 详解JavaScript中的Event Loop(事件循环)机制 。

JavaScript是一种单线程、非阻塞的语言,这是由于它当初的设计就是用于和浏览器交互的:
• 单线程: JavaScript 设计为单线程的原因是,最开始它最大的作用就是和 DOM 进行交互,试想一下,如果 JavaScript 是多线程的,那么当两个线程同时对 DOM 进行一项操作,例如一个向其添加事件,而另一个删除了这个 DOM ,此时该如何处理呢?因此,为了保证不会 发生类似于这个例子中的情景, JavaScript 选择只用一个主线程来执行代码,这样就保证了程序执行的一致性。
• 非阻塞:当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起( pending )这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。而 JavaScript 实现异步操作的方法就是使用Event Loop。

setTimeout(function () {
    console.log(1);
});

new Promise(function(resolve,reject){
    console.log(2)
    resolve(3)
}).then(function(val){
    console.log(val);
})

下面通过一段代码来分析这个问题,首先 setTimeout 和 Promise 中的 then 回调都是异步方法,而 new Promise 则是一个同步操作,所以这段代码应该首先会立即输出 2 ; JavaScript 将异步方法分为了 marco task (宏任务:包括 setTimeout 和 setInterval 等)和 micro task (微任务:包括 new Promise 等),在 JavaScript 的执行栈中,如果同时存在到期的宏任务和微任务,则会将微任务先全部执行,再执行第一个宏任务,因此,两个异步操作中 then 的回调会率先执行,然后才执行 setTimeout 的回调,因此会依次输出3、1,所以最终输出的结果就是2、3、1。

6、ES6作用域及let和var的区别

这个问题阮一峰老师在ECMAScript 6 入门中的 let 和 const 命令 章节对这个问题作出了详细的说明,下面提取一些我认为关键的点进行讲解。

ES6引入了使用 {} 包裹的代码区域作为块级作用域的声明方式,其效果与ES5中 function 声明的函数所生成的函数作用域具有相同的效果,作用域外部不能访问作用域内部声明的函数或变量,这样的声明在ES6中对于包括 for () {} 、 if () {} 等大括号包裹的代码块中都会生效,生成一个单独的作用域。

ES6新增的 let 声明变量的方式相比 var 具有以下几个重要特点:
• let 声明的变量只在作用域内有效,如下方代码, if 声明生成了一个块级作用域,在这个作用域内声明的变量在作用域外部无法访问,假如访问会产生错误:

if (true) {
	let me = 'handsome boy';
}
console.log(me); // ReferenceError
• let 声明的变量与 var 不同,不会产生变量提升,如下方代码,在声明之前输出代码,会产生错误: 
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // ReferenceError
let bar = 2;
• let 的声明方式不允许重复声明,如重复声明会报错,而 var 声明变量时,后声明的语句会对先声明的语句进行覆盖: 
// 报错
function func() {
  let a = 10;
  var a = 1;
}

// 报错
function func() {
  let a = 10;
  let a = 1;
}
• 只要块级作用域内存在 let 命令,它所声明的变量就“绑定”( binding )这个区域,不再受外部的影响,这个特性称为 暂时性死区 。 
var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

7、闭包

待补充

8、原型及原型链

待补充

9、浏览器的回流与重绘 (Reflow & Repaint)

待补充

10、JS对象的深复制

一般的思路就是递归解决,对不同的数据类型做不同的处理:

function deepCopy (obj) {
  let result = {}
  for (let key in obj) {
    if (obj[key] instanceof Object || obj[key] instanceof Array) {
      result[key] = deepCopy(result[key])
    } else {
      result[key] = obj[key]
    }
  }
  return result
}

这个只能复制内部有数组、对象或其他基础数据类型的对象,假如有一些像 RegExp 、 Date 这样的复杂对象复制的结果就是一个 {} ,无法正确进行复制,因为没有对这些特殊对象进行单独的处理。若

如果要复制的对象数据结构较为简单,没有复杂对象的数据,那么可以用最简便的方法:
let cloneResult = JSON.parse(JSON.stringify(targetObj))

浏览器相关

1、浏览器从加载到渲染的过程,比如输入一个网址到显示页面的过程

加载过程:
•浏览器根据 DNS 服务器解析得到域名的 IP 地址
•向这个 IP 的机器发送 HTTP 请求
•服务器收到、处理并返回 HTTP 请求
•浏览器得到返回内容

2、性能优化

待补充

Vue

1、组件间通信方式

Vue的官方文档对组件间的通信方式做了详细的说明:cn.vuejs.org

父组件向子组件传输
• 最常用的方式是在子组件标签上传入数据,在子组件内部用 props 接收:

// 父组件
<template>
  <children name="boy"></children>
</template>

// 子组件:children
export default {
  props: {
    name: String
  }
}

• 还可以在子组件中用 this.$parent 访问父组件的实例,不过官方文档有这样一段文字,很好的说明了 $parent 的意义:节制地使用 $parent 和 $children —— 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信。

子组件向父组件传输
• 一般在子组件中使用 this.$emit(‘eventName’, ‘data’) ,然后在父组件中的子组件标签上监听 eventName 事件,并在参数中获取传过来的值。

// 子组件
export default {
  mounted () {
    this.$emit('mounted', 'Children is mounted.')
  }
}

// 父组件
<template>
  <children @mounted="mountedHandle"></children>
</template>

<script>
export default {
  methods: {
    mountedHandle (data) {
      console.log(data) // Children is mounted.
    }
  }
}
</script>

• 与 p a r e n t 一 样 , 在 父 组 件 中 可 以 通 过 访 问 t h i s . parent 一样,在父组件中可以通过访问 this. parent访this.children 来访问组件的所有子组件实例。

总结
以上就是我要说的内容,希望以上的内容可以帮助到正在默默艰辛的大家,希望大家在往后的工作与面试中一切顺利。
那如何学习才能快速入门并精通呢?
当真正开始学习的时候难免不知道从哪入手,导致效率低下影响继续学习的信心。
但最重要的是不知道哪些技术需要重点掌握,学习时频繁踩坑,最终浪费大量时间,所以有一套实用的视频课程用来跟着学习是非常有必要的。
本次给大家推荐一个免费的学习群,里面概括移动应用网站开发,css,html,webpack,vue node angular以及面试资源等。
对web开发技术感兴趣的同学,欢迎加入Q群:866109386,不管你是小白还是大牛我都欢迎,还有大牛整理的一套高效率学习路线和教程与您免费分享,同时每天更新视频资料。
最后,祝大家早日学有所成,拿到满意offer,快速升职加薪,走上人生巅峰。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值