前端面试题,持续更新中...

227 阅读7分钟

假期结束了,开始准备肝面试题了...

本文综合高赞面试题及自己总结输出而成...

一、 JavaScript 相关

1. var、let、const之间的区别

1. 变量提升

var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined;

letconst不存在变量提升,即它们声明的变量一定要在声明之后使用,否则会报错。

// var的情况
console.log(a); // undefined
var a = 1;

// let和const的情况
console.log(b); // Uncaught ReferenceError: b is not defined
console.log(c); // Uncaught ReferenceError: c is not defined
let b = 1;
const c = 1;

2. 暂时性死区

什么是暂时性死区?

ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前使用这些变量,就会报错。总之,在代码块中,用let和const声明的变量,该变量都是不可用的,这在语法上称为暂时性死区(temporal dead zone,简称 TDZ)。

var不存在暂时性死区;

let和const存在暂时性死区。

if (true) {
  // TDZ 开始
  tmp = 'abc'; // Uncaught ReferenceError: tmp is not defined
  console.log(tmp); // Uncaught ReferenceError: tmp is not defined

  let tmp; // TDZ 结束
  console.log(tmp) // undefined

   tmp = 123;
   console.log(tmp);  // 123
}

3. 块级作用域

var不存在块级作用域(ES5中只存在全局作用域和函数作用域);

letconst存在块级作用域。

4. 重复声明

var允许重复声明变量;

letconst再同一作用域不允许重复声明变量。

// var
var a = 10;
var a = 20; // 20 

// let
let b = 10;
let b = 20; // Identifier 'b' has already been declared
// const
const c = 10;
const c = 20; // Identifier 'c' has already been declared

5. 修改声明的变量

varlet可以修改;

const一旦声明,常量的值就不能改变。

const实际上保证的不是变量值不可改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量;对于复杂类型的数据,变量指向的内存地址,保存的只是一个实际数据的指针,const只能保证这个指针是固定的,不能控制指向的数据结构是不是可变的。

const foo = {};

// 为foo添加一个属性,可以成功
foo.prop = 123;
console.log(foo.prop); // 123

// 将foo指向另一个对象,就会报错
foo = {}; // Uncaught TypeError: Assignment to constant variable.

2. 深浅拷贝

浅拷贝和深拷贝都是对于JS中的引用数据类型而言的。浅拷贝只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。

1. 什么是深浅拷贝

浅拷贝:只复制引用,未复制真正的值。

深拷贝:对目标的完全拷贝,不像浅拷贝那样只复制了一层引用,就连值也都复制了。

2. 实现浅拷贝的方法

利用 = 赋值操作符实现浅拷贝。

3. 实现深拷贝的方法

(1)利用json中的parsestringify

JSON.stringify 是将一个 JavaScript 值转成一个 JSON 字符串。

JSON.parse 是将一个 JSON 字符串转成一个 JavaScript 值或对象。

const originObj = {a: 'a', b: 'b', c: [1,2,3], d: {dd: 'dd'}};
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj === originObj); // false

cloneObj.a = 'aa';
cloneObj.c = [1,1,1];
cloneObj.d.dd = 'doubled';

console.log(cloneObj); // {a: 'aa', b: 'b', c: [1,1,1], d: {dd: 'doubled'}};
console.log(originObj); // {a: 'a', b: 'b', c: [1,2,3], d: {dd: 'dd'}};

如果对象中含有undefined函数symbol,在转换过程中会被忽略,不能通过这种方法进行深拷贝。

const originObj = {
  name: 'axuebin',
  sayHello: function(){
    console.log('Hello World');
  }
}
console.log(originObj); // {name: "axuebin", sayHello: ƒ}
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj); // {name: "axuebin"}

(2)利用递归来实现每一层都重新创建对象并赋值。

function deepClone(source) {
  const targetObj = source.constructor === Array ? [] : {};
  for (let keys in source) { // 遍历目标
    if (source.hasOwnProperty(keys)) { // 判断目标是否有这个属性
      if (source[keys] && typeof source[keys] === 'object') { // 如果值是对象就递归一下
        targetObj[keys] = source[keys].constructor === Array ? [] : {};
        targetObj[keys] = deepClone(source[keys]);
      } else { // 如果不是就直接赋值
        targetObj[keys] = source[keys];
      }
    }
  }
  return targetObj;
}

4. JavaScript中的拷贝方法

数组中concatslice两个方法可以实现对原数组的拷贝,这两个方法不会改变原数组,会返回一个新的数组。

(1)concat

const originArray = [1,2,3,4,5];
const cloneArray = originArray.concat();

console.log(cloneArray === originArray); // false
cloneArray.push(6); // [1,2,3,4,5,6]
console.log(originArray); [1,2,3,4,5];

对于多层数组来说:

const originArray = [1,[1,2,3],{a:1}];
const cloneArray = originArray.concat();
console.log(cloneArray === originArray); // false
cloneArray[1].push(4);
cloneArray[2].a = 2; 
console.log(originArray); // [1,[1,2,3,4],{a:2}]

(2)slice

concat方法。

结论:concat和slice只是对数组的第一层进行深拷贝。

(3)Object.assign()

Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象,它将返回目标对象。

如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。

语法:Object.assign(target, ...source)

const target = {a: 'a', b: 'b', c: {a: 1}};
const source = {a: 'a', b: 'bb', c: {a: 2}};
let returnedValue = Object.assign(target, source);
source.c.a = '3';
source.b = 'bbb';
console.log(returnedValue); // {a: "a", b: "bb", c: {a: '3'}}

(4)...展开运算符

Object.assign()方法。

结论:Object.assign()方法和...展开运算符只是对对象的第一层进行深拷贝。

🙆 总结

  1. 赋值运算符 = 实现的是浅拷贝,只拷贝对象的引用值;
  2. JavaScript中数组和对象自带的拷贝方法都是“首层浅拷贝”;
  3. JSON.parse(JSON.stringify())实现的是深拷贝,但是对目标对象有要求;
  4. 若想真正意义上的深拷贝,请递归。

3.作用域链 / 执行上下文 / 闭包

1. 作用域链

2. 执行上下文

(1)什么是执行上下文

执行上下文是评估和执行JavaScript代码的环境的抽象概念。每当JavaScript代码在执行的时候,都是在执行上下文中进行的。

(2)执行上下文的类型

JavaScript中有三种类型的执行上下文。

  • 全局执行上下文
  • 函数执行上下文
  • Eval 函数执行上下文

(3)执行栈

(4)如何创建执行上下文

  • 创建阶段
  1. this绑定
  2. 创建词法环境组件
  3. 创建变量环境组件
  • 执行阶段

3. 闭包

三、HTML5 相关

1. HTML5新特性

  • 语义标签
  • 增强型表单
  • 视频和音频
  • Canvas绘画
  • SVG绘图
  • 地理定位
  • 拖放API
  • WebWorker
  • WebStorage
  • WebSocket

2. HTML5语义化

1. 什么是语义化

指合理的使用语义化的标签来创建页面结构,从标签上可以直观的看出标签的作用,而不是滥用div

2. 语义化标签

headernavmainarticlesectionasidefooter

3. 语义化的优点

  • 代码结构清晰,易于阅读,利于开发和维护;
  • 方便其他设备解析,根据语义渲染网页;
  • 有利于搜索引擎优化(SEO),搜索引擎爬虫会根据不同的标签赋予不同的权重。

四、CSS3 相关

1. Flex布局

30分钟学会Flex布局

五、一些经典

1. 从输入URL到页面展示,这中间发生了什么?

史上最详细的经典面试题 从输入URL到看到页面发生了什么?

六、HTTP协议

1. 状态码

2. HTTP缓存

image.png

HTTP协议存在简单的机制可以在不要求服务器记住有哪些缓存拥有其文档副本的情况下,保持已缓存的副本与服务器数据充分一致,HTTP将这些简单的机制称为文档过期服务器再验证