前端基础面试总结

385 阅读14分钟

JS基础知识

变量类型和计算

常见题目:

  • js中使用typeof能得到的那些类型
  • 何时使用=== 和== (a == null 与 a=== null || a === undefined 可以互相替换)
  • js有哪些内置函数 (数据封装类对象):Object、Array、Boolean、Number、String、Function、Date、RegExp、Error(Math为js内置对象)
  • js变量按照存储方式区分为哪些类型,并描述其特点
  • 如何理解JSON(js中的内置对象):JSON.stringify()转换为字符串 JSON.parse('{"a": ""}')转换为对象

知识点:

  • 变量类型: 值类型 vs 引用类型 、typeOf运算符详解
(1)值类型:(每个变量都能存储各自的值,不会相互影响)

Number,Boolean,String,Undefined,Null

var a = 100
var b = a
a = 200
console.log(b) // 100

(2)引用类型(对象、数组、函数): (每个变量只能存储一个指针,指针指向被存储的值)

特点:可以无限制扩展属性(节省空间)

var a = { name: '小明'}
var b = a // a和b指向同一个对象
a.name = '老王'
console.log(b.name) // 老黄

(3)typeof运算符
typeof undefined // undefined
typeof 'abc' // string
typeof 123 // number
typeof true // Boolean
typeof {} // object
typeof [] // object
typeof null // object
typeof console.log // function
  • 变量计算
(1)强制类型转换:字符串拼接、==运算符、if语句、逻辑运算(&&、||、!、!!)


原型和原型链

常见题目:

  • 如何准确判断一个变量是数组类型

var arr = []
arr instanceof Arry // true
typeOf arr // Object

  • 写一个原型链继承的例子

// 不建议使用 只是进行较为简单的例子展示
function Animal() {
	this.eat = function (name) {
		console.log(name + 'eat')
	}
}
// 狗
function Dog() {
	this.bark = function () {
		console.log('dos bark')	
	}
}
Dog.prototype = new Animal()
// 泰迪
var taidi = new Dog()
taidi.eat('taidi')
taidi.bark()
taidi.__proto__ === Dog.prototype  // true
taidi.__proto__.__proto__ ===  Dog.prototype._proto_ === Animal.prototype // true

// 原生设置innerHtml/监听事件
function Elem(id) {
	this.elem = document.getElementById(id)
}
Elem.prototype.html = function(val) {
	var elem = this.elem
	if (val) {
		elem.innerHTML = val
		return this
	} else {
		return elem.innerHTML
	}
}
Elem.prototype.on = function(type, fn) {
	var elem = this.elem
	elem.addEventListener(type, fn)
        return this
}

var div1 = new Element('div1')

div1.html('插入文本')
div1.on('click', funtion() {
	console.log('绑定事件')
})
// 链式操作
div1.html('插入文本').on('click', funtion() {
	console.log('绑定事件')
}).html('由于返回了this')


  • 描述new一个对象的过程:  创建一个新对象、this指向这个新对象、执行代码(对this进行赋值)、返回this

var Dog = function(name) {
    this.name = name
}
Dog.prototype.bark = function () {
    console.log('wangwang')
}
Dog.prototype.sayName = function () {
    console.log('my name is' + this.name)
}
let sanma = new Dog('三毛')
sanmao.sayName()
sanmao.bark()

// new 的作用
// 创建一个新对象obj
// 把obj的_proto_指向Dog.prototype实现继承
// 执行构造函数,传递参数, 改变this指向 Dog.call(obj, ...args)
// 最后把obj赋值给新变量

var _new = function () {
    let constructor = Array.prototype.shift.call(arguments) // 取第一个参数
    let args = arguments
    const obj = new  Object()
    obj._proto_ = constructor.prototype
    constructor.call(obj, ...args)
    return obj
}

var simao = _new (Dog, 'simao')
simao.bark()
simao.sayName()
console.log(simao instanceof Dog) // true

  • zepto(或者其他框架)源码中如何使用原型链

知识点:

  • 构造函数(在定义构造函数的时候养成良好的代码习惯,首字母大写)

function Example(name, age) {
	// 先是默认 this为一个空对象
	this.name = name
	this.age = age
	this.sex = 'women'
	// return this // 默认有这一行
}
var f = new Example('老王', '40')
var f2 = new Example('小明', '8') // 创建多个对象
  • 构造函数扩展

(1)var a = {} 其实是var a = new Object()的语法糖

(2)var a = [] 其实是var a = new Array()的语法糖

(3)function Example() {...} 其实是var Example = new Function()的语法糖

(4)使用instanceof判断一个函数是否是一个变量的构造函数

  • 原型规则和示例

(1)所有的引用类型(数组、对象、函数),都具有对象特性,且具有自由扩展属性(除null);

var obj = {};obj.a = 100; 
var arr = [];arr.a = 100;
function fn() {}
fn.a = 100

(2)所有的引用类型(数组、对象、函数),都有一个_proto_(隐式原型)属性,属性值是一个普通的对象(浏览器自己创建);

obj._proto_
arr._proto_
fn._proto_

(3)所有的函数,都有一个prototype(显示原型)属性,属性值也是一个普通的对象(浏览器自己创建)

fn.prototype

(4)所有的引用类型(数组、对象、函数),_proto_属性值指向它的构造函数“prototype”属性值;

obj._proto_ === Object.prototype // true

(5)当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去他的——proto_(即它的构造函数的prototype)中寻找。

function Example(name, age) {
	// 先是默认 this为一个空对象
	this.name = name
	this.age = age
	this.sex = 'women'
	// return this // 默认有这一行
}
Example.prototype.alertName = function() {
    console.log(this.name) // alertName 是构造函数的属性
}
var f = new Example('老王', '40')
f.printName = function() {
	console.log(this.name) // printName 是f本身的一个属性
}
f.printName() // 老王
f.alertName() // 老王
// 通过属性的方法调用不论是自身的属性还是构造函数的属性其中的this都指向调用者
for(var item in f) {
	// 高级浏览器已经在 for in中屏蔽了构造函数中的属性
	if(f.hasOwnProperty(item)) { // 判断是否为f本身的属性
		console.log(item) // 不包含alertName
	}
}
  • 原型链


f.toString() // 要去f._proto_._proto_中查找

  • instanceof(用于判断引用类型属于哪个构造函数的方法)

判断逻辑: f的_proto_一层一层往上,能否对应到Example.prototype

f instanceof Example // true 
f instanceof Object // true

作用域和闭包

常见题目:

  • 说一下对变量提升的理解
  • 说明this几种不同的使用场景
  • 创建10个<a>标签,点击的时候弹出对应的序号

for (var i = 0; i<10; i++) {
	(function(i) {
		var a = document.createElement('a')
		a.innerHTML = i + '<br>'
		a.addEventListener('click', function (e) {
			e.preventDefault()
			alert(1)
		})
		document.body.appendChild(a)
	})(i)
}

  • 如何理解作用域
  • 实际开发中闭包的应用

// 闭包实际应用中主要应用于封装变量,收敛权限
function isFirstLoad() {
	var _list = []
	return function(id) {
		if (_list.indexOf(id) >= 0) {
			return false
		} else {
			_list.push(id)
			return true
		}
	}
}
// 使用
var firstLoad = isFirstLoad()
firstLoad(10) // true
firstLoad(10) // false
firstLoad(20) // true

知识点:

  • 执行上下文

范围: 一段<script>或者一个函数

全局:变量定义、函数声明 一段<script>

console.log(a) // undefined
var a = 1

fn('老王')
function fn(name) { // 函数声明
	age = 40
	console.log(name, age)
        var age
}

fn2('小明') // Uncaught TypeError: fn2 is not a function
var fn2 = function fn(name) { // 函数表达式
	age = 8
	console.log(name, age)
	var age
}

函数:变量定义、函数声明、this、arguments 函数

注意: 函数声明和函数表达式的区别

  • this

(1)作为构造函数执行

function F() {
    this.name = name
}
var f = new F('老王')

(2)作为对象属性执行

var a = {
      name: '老王',
      fn: function() {
       console.log(this.name)
      }
   } 
   a.fn() // this === a(3)作为普通函数执行 // window

funtion f() {
    console.log(this) // window
}
f()

(3)call apply bind (call和apply 很相似只是传参方式不同)

function fn(name, age) {
	console.log(name,age,this)
}
fn.call({x: 100}, '老王', 40) //老王 40 {x: 100}
fn.apply({x: 100}, ['老王', 40]) // 老王 40 {x: 100}

var fn2 = function (name, age) {
	console.log(name,age,this)
}.bind({y: 200}) // 函数声明不能使用.bind 只能采用函数表达式方式
fn2('老王', 40) //  老王 40 {y: 200}

this要在执行是才能确认值,定义时无法确认

  var a = {
      name: '老王',
      fn: function() {
       console.log(this.name)
      }
   } 
   a.fn() // this === a
   a.fn.call({name: '小明'}) // this === {name: 'B'}   
   var fn1 = a.fn    fn1() // this === window

  • 作用域

(1)没有块级作用域 (ES6新增了块级作用域)

(2)函数和全局作用域

  • 作用域链(一个函数的父级作用域为定义时的作用域而不是执行时的作用域)

var a = 1
function f1() {
    var b = 2
    function f2() {
        var c =3
        console.log(a) // a 是自由变量 1
        console.log(b) // b 是自由变量 2
        console.log(c) // 3
    }
    f2()
}

f1()

  • 闭包

(1) 函数作为返回值

function F1() {
    var a = 100
    return function () {
        console.log(a) 
    }
}
var f1 = F1()
var a = 200
f1() // 100

(2)函数作为参数传值

function F1() {
    var a = 100
    return function () {
        console.log(a) // 自由变量父级作用域寻找
    }
}
var f1 = F1()
function F2(fn) {
	var a = 200
	fn()
}
F2(f1)

异步和单线程

常见题目:

  • 同步和异步的区别是什么?分别举一个同步和异步的例子
  • 一个关于setTimeout的笔试题
  • 前端使用异步的场景有哪些

知识点:

  • 什么是异步(对比同步)

   同步会阻塞代码运行,异步不会

// 异步
console.log(100)
setTimeout(function() {
	console.log(200)
}, 1000)
console.log(300)
// 100 300 200

// 同步
console.log(100)
alert(200) // 弹窗点击确认后才会执行下面的代码
console.log(300)

  • 前端使用异步场景

(1)在可能发生等待的情况

(2)等待过程中不能像alert一样阻塞程序运行

(3)需要等待的情况下主要异步

具体使用场景:

(1)定时任务:setTimeout、setInverval

(2)网络请求: ajax请求、动态img加载

(3)事件绑定

  • 异步和单线程

其他知识

常见题目:

  • 获取2017-06-10格式的日期
  • 获取随取数,要求长度一致的字符串格式
  • 写一个能遍历对象和数组通用forEach函数
  • 已知如下数组:

    var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];

    编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组

// 第一种方法
Array.from(new Set(arr.flat(Infinity)).sort((a, b) => {
    return a - b
}))
// 第二种方法
new Set(arr.toString.split(',')).sort((a, b) => { return a-b })

知识点: 

  • 日期

Date.now() // 获取当前时间毫秒数 1970年
var dt = new Date() 
dt.getTime() // 获取毫秒数
dt.getFullYear() // 年
dt.getMonth() // 月(0-11)
dt.getDate() // 日(0-31)
dt.getHours() // 小时(0-23)
dt.getMinutes() // 分鐘(0-59)
dt.getSeconds() // 秒(0-59)

  • Math

Math.random() // 获取随机数

  • 数组API

数组详细可见:juejin.cn/post/684490…

(1)forEach遍历所有元素

(2)every 判断所有元素是否都符合条件

(3)some 判断是否有至少一个元素符合条件

(4)sort 排序

(5)map 对元素重新组装,生成新数组

(5)filter 过滤符合条件的元素

  • 对象API ( for in)



常说的JS(浏览器执行的JS)包含两部分:

(1)JS基础知识(ECMA标准)

(2)JS-Web-Api (W3C标准)


JS-Web-API

DOM操作:(Document Object Model)

常见题目:

  • DOM是哪种基本的数据结构?(树)
  • DOM操作的常用API有哪些?
  • DOM节点的attr和property有何区别?

知识点:

  • DOM的本质 (树)

DOM可以理解为:浏览器把拿到的html代码,结构化一个浏览器能识别并且js可操作的一个模型而已。

  • DOM节点操作

(1)获取DOM节点

  var dom1 = document.getElementById('#id') // 元素
  var domList = document.getElementsByTagName('tagName') // 集合
  var domList = document.getElementsByClassName('.className') // 集合
  var domList =document.querySelectorAll('') // 集合

(2)prototype(JS属性)

  var pList = document.querySelectorAll('p')
  var p = pList[0]
  console.log(p.style) // 获取样式  
  p.style.width = '100px'  console.log(p.className) // 获取class  
  p.className = 'p1' // 修改class
  // 获取nodeName 和 nodeType
  console.log(p.nodeName)  console.log(p.nodeType)

(3)Attribute(html标签属性)

var pList = document.querySelectorAll('p')
var p = pList[0]
p.getAttribute('data-name')
p.setAttribute('data-name', 'icon')
p.getAttribute('style')
p.setAttribute('style', 'font-size: 30px')
  • DOM结构操作

(1)新增节点

var div = document.getElementById('div')
// 添加新节点
var p1 = document.createElement('p')
p1.innerHTML = 'this is p1'
div.appendChild(p1)
// 移动已有节点
var p2 = document.getElementById('p2')
div.appendChild(p2)

(2)获取父元素(parentElement)

(3)获取子元素(childNodes集合)

(4)删除节点(removeChild)

BOM操作:(Browser Object Model)

常见题目:

  • 如何检测浏览器的类型(安卓、苹果)
  • 拆解url的各部分

知识点:

  • navigator

var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome') // 判断是否为谷歌浏览器 实际情况判断条件可能较复杂一些

  • screen (获取屏幕的相关数据)

console.log(screen.width)
console.log(screen.height)

  • location

console.log(location.href) // 地址
console.log(location.protocol) // 协议
console.log(location.origin) // 域名
console.log(location.pathname) // 路径
console.log(location.search) // 问号后面
console.log(location.hash) // 井号#后面

  • history

history.back() // 后退
history.forward() // 前进

事件:

常见题目:

  • 编写一个通用的事件监听函数
  • 描述事件冒泡流程
  • 对于一个无限下拉加载图片的页面,如何给每个图片绑定事件

知识点:

  • 通用事件绑定

var btn = document.getElementById('btn')
btn.addEventListener('click', function(event) {
	console.log('clicked')
})

IE低版本使用attachEvent绑定事件,和w3c标准不一样

function bindEvent(elem, type, fn) {
	elem.addEventListener(type, fn)
}
var a= document.getElementById('a')
bindEvent(a, 'click', function(e) {
	e.preventDefault() // 阻止默认行为
	alert('clicked')
})

  • 事件冒泡(点击事件触发后会一层一层的根据树形结构向上触发)

var p1 = document.getElementById('p1')
var body = document.body
bindEvent(p1, 'click', function(e) {
	e.stopPropatation()
	alert('激活')
})
bindEvent(body, 'click', function(e) {
	alert('取消')
})

  • 代理

<div id="div1"><a href="#"></a> <a href="#"></a> <a href="#"></a> <a href="#"></a> <a href="#"></a> <!-- 还会有更多的a标签 --> </div>

// 通过事件冒泡 并且通过target获取当前的点击标签
var div1 = document.getElementById('div1')
div1.addEventListener('click', function(e){
	var target = e.target
	if (target.node === 'A') {
		alert(target.innerHTML)
	}
})

  • 完善通用绑定事件的函数

  function bindEvent(elem, type, selector, fn) {    if (fn == null) {      fn = selector      selector = null    }    elem.addEventListener(type, function(e) {      var target      if (selector) {        target = e.target        if (target.matches(selector)) {          fn.call(target, e)        }      } else {        fn(e)      }    })  }  //  使用代理  var div1 = document.getElementById('div1')  bindEvent(div1, 'click', 'a', function(e) {    console.log(this.innerHTML)  })  var a = document.getElementById('a1')  bindEvent(div1, 'click',function(e) {    console.log(a.innerHTML)  })

Ajax

常见题目:

  • 手动编写一个ajax,不依赖第三方库
  • 跨域的几种实现方式

知识点:

  • XMLHttpRequest

IE低版本使用ActiveXObject, 与w3c不一样

  var xhr = new XMLHttpRequest()
  xhr.open("GET", "/api", false) // false 异步
  xhr.onreadystatechange = function() {
    // 这里的函数异步执行
    if (xhr.readyState == 4) {
      if (xhr.status == 200) {
        console.log(xhr.responseText)
      }
    }
  }  xhr.send(null)

  • 状态码说明

(1)readyState

0=>(未初始化)还没有调用send()方法

1=>(载入)已调用send()方法,正在发送请求

2=>(载入完成)send()方法执行完成,已经接收到全部响应内容

3=> (交互)正在解析响应内容

4=>(完成)响应内容解析完成,可以在客户端调用了

(2)status

2XX=>表示成功处理请求。如200

3XX=>需要重定向,浏览器直接跳转

4XX=>客户端请求错误,如404

5XX=>服务端错误

  • 跨域

(1)什么是跨域

浏览器有同源策略,不允许ajax访问其他域接口

跨域条件:协议、域名、端口(域名冒号后面的:http80/https443),有一个不同就算跨域

有三个标签允许跨域加载资源:img(可以用于打点统计,统计网站可能是其他域如:百度统计)/link(可以使用CDN、script也可以)/script(可以用于JSONP)

所有的跨域请求都必须经过信息提供方允许

(2) JSONP


  <script>  window.callBack = function(data) {    // 这是我们跨域得到信息    console.log(data)  }  </script>  <script src="{{跨域请求的地址}}"></script>

(3)服务器端设置http header(另外一种解决跨域的简洁方法,需要服务器端来做)

存储

常见题目:

  • 请描述一下cookie、sessionStorage和localStorage的区别?(容量、Ajax请求是否携带、使用性)

知识点:

  • cookie

(1)本身用于客户端和服务端通信

(2)但是它有本地存储的功能,于是就被借用

(3)使用document.cookie =  获取和修改

(4)缺点:

  1. 存储量小,只有4KB
  2. 所有http请求都带着,会影响获取资源的效率
  3. API简单,需要才能用document.cookie = ...
  • sessionStorage (会话储存)和localStorage(ios safari 隐藏模式下会报错建议使用try catch)

(1)HTML5专门为存储而设计,最大容量5M

(2)API简单易用:localStorage.setItem(key,value);localStorage.getItem(key)

运行环境

(1)浏览器可以通过访问链接来得到页面的内容

(2)通过绘制和渲染,显示出页面最终的样子

页面加载过程

常见题目:

  • 从输入url到得到html的详细过程
  • window.onload和DOMContentLoaded的区别

window.addEventListener('load', function () {  // window.onload
// 页面的全部资源加载完才会执行,包括图片、视频等
})
window.addEventListener('DOMContentLoaded', function () {
// DOM 渲染完即可执行,此时图片、视频还可能没有加载完
})

知识点:

  • 加载资源的形式
  1. 输入url(或跳转页面)加载html
  2. 加载html的静态资源(js、css、图片)
  • 加载一个资源的过程
  1. 浏览器根据DNS服务器得到域名的IP地址
  2. 向这个IP的机器发送http请求
  3. 服务器收到、处理并返回http请求
  4. 浏览器得到返回内容
  • 浏览器渲染页面的过程
  1. 根据html结构生成DOM Tree
  2. 根据CSS生成CSSOM
  3. 将DOM和CSSOM整合形成RenderTree(比DOM Tree多了样式)
  4. 根据RenderTree开始渲染和展示
  5. 遇到script时,会执行并阻塞渲染(因为JS可以改变DOM结构)

思考:

为何要把css放在head中?为何把js放在body下面?

性能优化

原则:

  • 多使用内存、缓存或者其他方法
  • 减少CPU计算、减少网络请求(减少IO硬盘读取)

从哪些入手?

  • 加载页面和静态资源

(1)静态资源的压缩合并

(2)静态资源缓存

(3)使用CDN让资源加载更快

(4)使用SSR即Server-Side Rendering(服务器端渲染)后端渲染,数据直接输出到HTML中

  • 页面渲染

(1)css放前面,JS放后面

(2)懒加载(图片懒加载,下拉加载更多)

(3)减少DOM查询,对DOM查询做缓存

(4)减少DOM操作,多个操作尽量合并在一起执行

(5)事件节流

(6)尽早执行操作(如DOMContentLoaded)

示例:

(1)资源合并(webpack commonJs)使用构建工具

(2)缓存(浏览器缓存)

  • 通过链接名称控制缓存
  • 只有内容改变的时候,链接名称才会改变

(3)CDN

(4)使用SSR后端渲染(可以把数据直接输出到HTML中)

(5)懒加载

  <img id="img1" src="preview.png" data-realsrc="abc.png" alt="">
  <script>
    var img1 = document.getElementById('img1')
    img1.src = img1.getAttribute('data-realsrc')
  </script>

(6)缓存DOM查询

    for (var i = 0; i < document.getElementsByTagName('p').length; i++) {
      // 查询了10次DOM
    }
    var pList = document.getElementsByTagName('p')
    for (var i = 0; i < pList.length; i++) {      // 查询了1次DOM    }

(7)合并DOM插入

    var list = document.getElementById('list')    // 要插入10个li标签    // 创建一个片段
    var frag = document.createDocumentFragment()
    var x, li    for (x = 0; x<10; x++) {
      li = document.createElement('li')
      li.innerHTML = "list item" + x
      frag.appendChild(li)
    }// 最后操作插入一次DOM
    list.appendChild(frag)

(8)事件节流和防抖


安全性(不重要)

知识点:

  • XSS跨站请求攻击
  • XSRF跨站请求伪造