Javascript一些基础面试题

JS面试

一些使Javascript更加简洁的小技巧

清空或截断数组


const arr = [11, 22, 33, 44, 55, 66];

// truncanting

arr.length = 3;

console.log(arr); // => [11, 22, 33]

//clearing

arr.length = 0;

console.log(arr); // => []

console.log(arr[2]); // => undefined

几个面试题

  1. JS中使用 typeof 能得到的哪些类型? 考点:JS变量类型
  2. 何时使用 === 何时使用 == ? 考点:强制类型转换
  3. window.onload和DOMContentLoaded的区别? 考点:浏览器渲染过程
  4. 用JS创建10个标签,点击的时候弹出来对应的序号 考点:作用域
  5. 简述如何实现一个模块加载器,实现类似require.js的基本功能 考点:JS模块化
  6. 实现数组的随机排序 考点:JS基础算法

变量类型和计算

题目

  1. JS中使用 typeof 能得到的哪些类型? 考点:JS变量类型
  2. 何时使用 === 何时使用 == ? 考点:强制类型转换
  3. JS中有哪些内置函数
  4. JS变量按照存储方式区分为哪些类型,并描述其特点
  5. 如何理解JSON

解答

  1. undefined、string、number、boolean、object、function
  2. obj.a == null 除了判断null和undefined外,都需要使用全等符
  3. 数据封装类对象- Object、Array、Boolean、Number、String、Function、Date、RegExp、Error
  4. 值类型、引用类型
  5. JSON 只不过是一个 JS 对象而已, 也是一个数据格式

知识点

  1. 变量类型
  2. 变量计算

变量类型

  • 值类型 vs 引用类型
  • typeof 运算符详解

变量计算 - 强制类型转换

  • 字符串拼接
  • == 运算符
  • if语句
  • 逻辑运算

原型和原型链

题目

  • 如何准确判断一个变量是数组类型
  • 写一个原型链继承的例子
  • 描述new一个对象的过程
  • zepto(或其他框架) 源码中如何使用原型链

解答

  • arr instanceof Array

// 动物

function Animal() {

this.eat = function () {

console.log('animal eat')

}

}

// 狗

function Dog() {

this.bark = function () {

console.log('dog bark')

}

}

Dog.prototype = new Animal()

// 哈士奇

var hashiqi = new Dog()

// 接下来代码演示时,会推荐更加贴近实战的原型继承示例!

描述new 一个对象的过程

  1. 创建一个新对象
  2. this 指向这个新对象
  3. 执行代码,即对this 赋值
  4. 返回 this

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

  • 阅读源码是高效提高技能的方式
  • 但不能 "埋头苦钻" 有技巧在其中
  • 慕课网搜索 "zepto设计和源码分析"

知识点

  • 构造函数
  • 构造函数 - 扩展
  • 原型规则和示例
  • 原型链
  • instanceof

原型规则和示例

  • 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了 "null" 以外)
  • 所有的引用(数组、对象、函数),都有一个__proto__ (隐式原型)属性,属性值是一个普通的对象
  • 所有的函数,都有一个 prototype (显式原型) 属性,属性值也是一个普通对象
  • 所有的引用类型(数组、对象、函数), __ proto __ 属性值指向它的构造函数的 "prototype" 属性值

var obj = {}; obj.a = 100;

var arr = []; arr.a = 100;

function fn () {}

fn.a = 100;

console.log(obj.__proto__);

console.log(arr.__proto__);

console.log(fn.__proto__);

console.log(fn.prototype)

console.log(obj.__proto__ === Object.prototype)
  • 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__ proto __ (即它的构造函数的 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(); // zhangsan

f.alertName(); // zhangsan

var item;

for (item in f) {

//高级浏览器已经在 for in 中屏蔽了来自原型的属性

//但是这里建议大家还是加上这个判断,保证程序的健壮性

if (f.hasOwnProperty(item)) {

console.log(item)

}

}

原型链


//构造函数

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()

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

instanceof

  • 用于判断 引用类型 属于哪个 构造函数 的方法
  • f instanceof Foo 的判断逻辑是:
  • f的 __ proto __ 一层一层往上,能否对应到 Foo.prototype
  • 再试着判断 f instanceof Object

作用域和闭包-执行上下文

题目

  1. 说一下对变量提升的理解
  • 变量定义
  • 函数声明(注意和函数表达式的区别)
  1. 说明 this 几种不同的使用场景
  • 作为构造函数执行
  • 作为对象属性执行
  • 作为普通函数执行
  • call apply bind
  1. 创建10个标签,点击的时候弹出来对应的序号

// 这是一个错误的写法!!!

var i,a

for (i = 0; i < 10; i++) {

a = document.createElement('a')

a.innerHTML = i + '<br>'

a.addEventListener('click', function (e) {

e.preventDefault()

alert(i)

})

document.body.appendChild(a)

}

// 这是正确的写法!!!

var i

for (i = 0; i < 10; i++) {

(function(i){

var a = document.createElement('a')

a.innerHTML = i + '<br>'

a.addEventListener('click', function (e) {

e.preventDefault()

alert(i)

})

document.body.appendChild(a)

})(i)

}
  1. 如何理解作用域
  • 自由变量
  • 作用域链,即自由变量的查找
  • 闭包的两个场景
  1. 实际开发中闭包的应用

// 闭包实际应用中主要用于封装变量,收敛权限

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

知识点

  • 执行上下文
  • this
  • 作用域
  • 作用域链
  • 闭包

执行上下文


console.log(a) // undefined

var a = 100

fn('zhangsan') // 'zhangsan' 20

function fn(name) {

age = 20

console.log(name, age)

var age

}
  • 范围:一段或者一个函数
  • 全局: 变量定义、函数声明 ==一段==
  • 函数: 变量定义、函数声明、this、arguments ==函数==
  • PS:注意"函数声明"和"函数表达式"的区别

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
  • 作为构造函数执行
  • 作为对象属性执行
  • 作为普通函数执行
  • call apply bind

作用域

  • 没有块级作用域
  • 只有函数和全局作用域

// 无块级作用域

if (true) {

var name = 'zhangsan'

}

console.log(name)

// 函数和全局作用域

var a = 100

function fn() {

var a = 200

console.log('fn', a)

}

console.log('global', a)

fn()

//作用域链

var a = 100

function fn() {

var b = 200

// 当前作用域没有定义的变量,即 "自由变量"

console.log(a)

console.log(b)

}

fn()

闭包


function F1() {

var a = 100

// 返回一个函数 (函数作为返回值)

return function () {

console.log(a)

}

}

// f1 得到一个函数

var f1 = F1()

var a = 200

f1()
  • 函数作为返回值
  • 函数作为参数传递

异步和单线程

什么是异步

题目

  1. 同步和异步的区别是什么?分别举一个同步和异步的例子
  • 同步会阻塞代码执行,而异步不会
  • alert是同步,setTimeout是异步
  1. 一个关于setTimeout的笔试题

console.log(1)

setTimeout(function () {

console.log(2)

}, 0)

console.log(3)

setTimeout(function () {

console.log(4)

}, 1000)

console.log(5)
  1. 前端使用异步的场景有哪些
  • 定时任务:setTimeout, setInverval
  • 网络请求:ajax请求,动态加载
  • 事件绑定

知识点

  • 什么是异步(对比同步)
  • 前端使用异步的场景
  • 异步和单线程

// 异步-不阻塞

console.log(100)

setTimeout(function() {

console.log(200)

}, 1000)

console.log(300)

// 同步-阻塞

console.log(100)

alert(200)

console.log(300)

何时需要异步

  • 在可能发生等待的情况
  • 等待过程中不能像 alert 一样阻塞程序运行
  • 因此,所有的 "等待的情况" 都需要异步

前端使用异步的场景

  • 定时任务:setTimeout, setInverval
  • 网络请求:ajax请求,动态加载
  • 事件绑定

// 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.onload = function(){

console.log('loaded')

}

img.src = '/xxx.png'

console.log('end')

// 事件绑定示例

console.log('start')

document.getElementById('btn1').addEventListener('click', function() {

alert('clicked')

})

console.log('end')

单线程

  • 执行第一行,打印100
  • 执行setTimeout后,传入setTimeout的函数会被暂存起来,不会立即执行(单线程的特点,不能同时干两件事)
  • 执行最后一行,打印300
  • 待所有程序执行完,处于空闲状态时,会立马看有没有暂存起来的要执行。
  • 发现暂存起来的setTimeout中的函数无需等待时间,就立即拿过来执行

其他知识

题目

  1. 获取 2017-06-10 格式的日期

function formatDate(dt) {

if (!dt) {

dt = new Date()

}

var year = dt.getFullYear()

var month = dt.getMonth() + 1

var date = dt.getDate()

if (month < 10) {

// 强制类型转换

month = '0' + month

}

if (date < 10) {

// 强制类型转换

date = '0' + date

}

// 强制类型转换

return year + '-' + month + '-' + date

}

var dt = new Date()

var formatDate = formatDate(dt)

console.log(formatDate)
  1. 获取随机数,要求是长度一致的字符串格式

var random = Math.random()

var random = random + '0000000000' // 后面加上10个零

var random = random.slice(0, 10)

console.log(random)
  1. 写一个能遍历对象和数组的通用forEach函数

function forEach(obj, fn) {

var key

if (obj instanceof Array) {

// 准确判断是不是数组

obj.forEach(function (item, index) {

fn(index, item)

})

} else {

// 不是数组就是对象

for (key in obj) {

fn(key, obj[key])

}

}

}

var arr = [1,2,3]

// 注意,这里参数的顺序换了,为了和对象的遍历格式一致

forEach(arr, function (index, item) {

console.log(index, item)

})

var obj = {x: 100, y: 200}

forEach(obj, function (key, value) {

console.log(key, value)

})

知识点

  • 日期
  • Math
  • 数组API
  • 对象API

日期


Date.now() // 获取当前时间毫秒数

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

  • forEach 遍历所有元素
  • every 判断所有元素是否都符合条件
  • some 判断是否有至少一个元素符合条件
  • sort 排序
  • map 对元素重新组装,生成新数组
  • filter 过滤符合条件的元素

forEach


var arr = [1,2,3]

arr.forEach(function (item, index) {

// 遍历数组的所有元素

console.log(index, item)

})

every


var arr = [1,2,3]

var result = arr.every(function (item, index) {

// 用来判断所有的数组元素,都满足一个条件

if (item < 4) {

return true

}

})

console.log(result)

some


var arr = [1,2,3]

var result = arr.some(function (item, index) {

// 用来判断所有的数组元素,只要有一个满足条件即可

if (item < 2) {

return true

}

})

console.log(result)

sort


var arr = [1,4,2,3,5]

var arr2 = arr.sort(function(a, b) {

// 从小到大排序

return a - b

// 从大到小排序

// return b - a

})

console.log(arr2)

map


var arr = [1,2,3,4]

var arr2 = arr.map(function(item, index) {

// 将元素重新组装,并返回

return '<b>' + item + '</b>'

})

console.log(arr2)

filter


var arr = [1,2,3,4]

var arr2 = arr.filter(function(item, index) {

// 通过某一个条件过滤数组

if (item >= 2) {

return true

}

})

console.log(arr2)

对象API


var obj = {

x: 100,

y: 200,

z: 300

}

var key

for (key in obj) {

// 注意这里的 hasOwnProperty, 再讲原型链时候讲过了

if (obj.hasOwnProperty(key)) {

console.log(key, obj[key])

}

}

JS-Web-API

  • JS基础知识: ECMA 262 标准
  • JS-Web-API: W3C 标准
  • W3C标准中关于JS的规定有:
  • DOM操作
  • BOM操作
  • 事件绑定
  • ajax请求(包括http协议)
  • 存储

DOM操作

题目

  1. DOM 是哪种基本的数据结构?
  1. DOM 操作的常用API有哪些
  • 获取DOM节点,以及节点的property和Attribute
  • 获取父节点,
  • 新增节点,删除节点
  1. DOM 节点的Attribute 和 property有何区别
  • property 只是一个JS对象的属性的修改
  • Attribute 是对html标签属性的修改

知识点

  • DOM本质
  • DOM节点操作
  • DOM结构操作

DOM节点操作

  • 获取DOM节点
  • property
  • Attribute

var div1 = document.getElementById('div1') // 元素

var divList = document.getElementsByTagName('div') // 集合

console.log(divList.length)

console.log(divList[0])

var containerList = document.getElementsByClassName('.container') // 集合

var pList = document.querySelectorAll('p') // 集合

// property

var pList = document.querySelectorAll('p')

var p = pList[0]

console.log(p.style.width) // 获取样式

p.style.width = '100px'

console.log(p.className)

p.className = 'p1'

// 获取 nodeName 和 nodeType

console.log(p.nodeName)

console.log(p.nodeType)

// Attribute

var pList = document.querySelectorAll('p')

var p = pList[0]

p.getAttribute('data-name')

p.setAttribute('data-name', 'sss')

p.getAttribute('style')

p.setAttribute('style', 'font-size:28px;')

DOM结构操作

  • 新增节点
  • 获取父元素
  • 获取子元素
  • 删除节点

// 新增节点

var div1 = document.getElementById('div1')

// 添加新节点

var p1 = document.createElement('p')

p1.innerHtml = 'this is p1'

div1.appendChild(p1) // 添加新创建元素

// 移动已有节点

var p2 = document.getElementById('p2')

div1.appendChild(p2)

// 获取父元素和子元素

var div1 = document.getElementById('div1')

var parent = div1.parentElement

var child = div1.childNodes

div1.removeChild(child[0])

BOM操作

题目

  1. 如何检测浏览器的类型

var ua = navigator.userAgent

var isChrome = ua.indexOf('Chrome')

console.log(isChrome)
  1. 拆解url的各部分

location.href

location.protocol

location.pathname

location.search

location.hash

知识点

  • navigator
  • screen
  • location
  • history

// navigator

var ua = navigator.userAgent

var isChrome = ua.indexOf('Chrome')

console.log(isChrome)

// screen

console.log(screen.width)

console.log(screen.height)

// location

console.log(location.href)

console.log(location.protocol) // 'http:' 'https:'

console.log(location.pathname) // user/api

console.log(location.search)

console.log(location.hash)

// history

history.back()

history.forward()

事件

题目

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

知识点

  • 通用事件绑定
  • 事件冒泡
  • 代理

通用事件绑定


var btn = document.getElementById('btn1')

btn.addEventListener('click', function (event) {

console.log('clicked')

})

function bindEvent(elem, type, fn) {

elem.addEventListener(type, fn)

}

var a = document.getElementById('link1')

bindEvent(a, 'click', function(e) {

e.preventDefault() // 阻止默认行为

alert('clicked')

})

关于IE低版本的兼容性

  • IE低版本使用 attachEvent绑定事件,和W3C标准不一样
  • IE低版本使用量以非常少,很多网站都早已不支持
  • 建议对IE低版本的兼容性: 了解即可,无需深究
  • 如果遇到对IE低版本要求苛刻的面试,果断放弃

代理


var div1 = document.getElementById('div1')

div1.addEventListener('click', function(e) {

var target = e.target

if (target.nodeName === '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)

}

})

}

代理的好处

  • 代码简洁
  • 减少浏览器内存占用

Ajax

题目

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

知识点

  • XMLHttpRequest
  • 状态码说明
  • 跨域

XMLHttpRequest


var xhr = new XMLHttpRequest()

xhr.open("GET", "/api", false)

xhr.onreadystatechange = function () {

// 这里的函数异步执行,可参考之前JS基础中的异步模块

if (xhr.readyState == 4) {

if (xhr.status == 200) {

alert(xhr.responseText)

}

}

}

xhr.send(null)

IE兼容性问题

  • IE低版本使用 ActiveXObject, 和W3C标准不一样
  • IE低版本使用量以非常少,很多网站都早已不支持
  • 建议对IE低版本的兼容性: 了解即可,无需深究
  • 如果遇到对IE低版本要求苛刻的面试,果断放弃

readyState

  • 0 - (未初始化)还没有调用send()方法
  • 1 - (载入)已调用send()方法,正在发送请求
  • 2 - (载入完成)send()方法执行完成,已经接收到全部响应内容
  • 3 - (交互) 正在解析响应内容
  • 4 - (完成) 响应内容解析完成,可以在客户端调用了

status

  • 2xx - 表示成功处理请求。如200
  • 3xx - 需要重定向,浏览器直接跳转
  • 4xx - 客户端请求错误,如404
  • 5xx - 服务器端错误

跨域

  • 什么是跨域
  • JSONP
  • 服务器端设置 http header (cors)

什么是跨域

  • 浏览器有同源策略,不允许ajax访问其他域接口
  • 跨域条件:协议、域名、端口、有一个不同就算跨域

可以跨域的三个标签

  • 但是有三个标签允许跨域加载资源

三个标签的场景

  • 用于打点统计,统计网站可能是其他域

跨域注意事项

  • 所有的跨域请求都必须经过信息提供方允许
  • 如果未经允许即可获取,那是浏览器同源策略出现漏洞

JSONP实现原理

存储

题目

  1. 请描述一下 cookie, sessionStorage 和 localStorage的区别?
  • 容量
  • 是否会携带到ajax中
  • API易用性

知识点

  • cookie
  • localStorage 和 sessionStorage
  • 本身用于客户端和服务器端通信
  • 但是它有本地存储的功能,于是就被 "借用"
  • 使用 document.cookie = .... 获取和修改即可
  • 存储量太小,只有4kb
  • 所有http请求都带着,会影响获取资源的效率
  • API简单,需要封装才能用 document.cookie = ....

localStorage 和 sessionStorage

  • HTML5专门为存储而设计,最大容量5M
  • API简单易用
  • localStorage.setItem(key, value); localStorage.getItem(key);
  • IOS safari 隐藏模式下
  • localStorage.getItem 会报错
  • 建议统一使用 try-catch 封装

关于开发环境

  • 面试官想通过开发环境了解面试者的经验
  • 开发环境,最能提现工作产出的效率

模块化

知识点

  • 不使用模块化的情况
  • 使用模块化
  • AMD
  • CommonJS

运行环境

页面加载 - 渲染过程

题目

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

知识点

  • 加载资源的形式
  • 加载一个资源的过程
  • 浏览器渲染页面的过程

加载资源的形式

加载一个资源的过程

  1. 浏览器根据DNS服务器得到域名的IP地址
  2. 向这个IP的机器发送http 请求
  3. 服务器收到、处理并返回http 请求
  4. 浏览器得到返回内容

浏览器渲染页面的过程

答案

  1. 根据HTML结构生成DOM Tree
  2. 根据CSS生成 CSSOM
  3. 将DOM和CSSOM整合形成RenderTree
  4. 根据 RenderTree 开始渲染和展示
  5. 遇到 时,会执行并阻塞渲染

window.onload // 页面的全部资源加载完才会执行,包括图片、视频等

DOMContentLoaded // DOM渲染完即可执行,此时图片、视频还可能没有加载完

性能优化

原则

  • 多使用内存、缓存或者其他方法
  • 减少CPU计算、减少网络

从哪里入手

  • 加载页面和静态资源
  • 页面渲染

加载资源优化

  • 静态资源的压缩合并
  • 静态资源缓存
  • 使用CDN让资源加载更快
  • 使用SSR后端渲染,数据直接输出到HTML中

渲染优化

  • CSS放前面,JS放后面
  • 懒加载(图片懒加载、下拉加载更多)
  • 减少DOM查询,对DOM查询做缓存
  • 减少DOM操作,多个操作尽量合并在一起执行
  • 事件节流
  • 尽早执行操作(如DOMContentLoaded)

合并DOM插入


//合并DOM插入

var listNode = document.getElementById('list')

// 要插入10个 li 标签

var frag = document.createDocumentFragment() // 不触发DOM操作

var x, li;

for (x = 0; x < 10; x++) {

li = document.createElement("li")

li.innerHTML = "list item " + x

frag.appendChild(li)

}

listNode.appendChild(frag) //触发DOM操作

事件节流


var textarea = document.getElementById('text')

var timeoutId

textarea.addEventListener('keyup', function () {

if (timeoutId) {

clearTimeout(timeoutId)

}

timeoutId = setTimeout(function () {

// 触发 change 事件

}, 100)

})

安全性

知识点

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

XSS

  • 在新浪博客写一篇文章,同时偷偷插入一段
  • 攻击代码中,获取cookie, 发送自己的服务器
  • 发布博客,有人查看博客内容
  • 会把查看者的cookie发送到攻击者的服务器

预防

  • 前端替换关键字,例如替换<为< > 为>
  • 后端替换

XSRF

预防

  • 增加验证流程,如输入指纹、密码、短信验证码

浏览器中的线程

  • GUI渲染线程
  • javascript引擎线程
  • HTTP请求线程
  • 浏览器事件触发线程

异步API

  • DOM Events
  • XMLHttpRequest(ajax)
  • fetch
  • WebSockets
  • Service Worker
  • Timer

Promise

  • 异步处理方案
  • 异步数据的链式处理
  • 异步代码同步化
  • catch 获取异常
  • then 正常结束

event-loop

事件轮巡

  • 异步队列

jquery-deferred

  • dtd的API可分成两类,用意不同
  • 第一类:dtd.resolve dtd.reject
  • 第二类:dtd.then dtd.done dtd.fail
  • 这两类应该分开,否则后果严重
  • 使用 返回dtd.promise() 解决随意在监听范围调用reject

async/await

ES7提案

  • then 只是将 callback拆分了
  • async/await 是最直接的同步写法

用法

  • 使用 await , 函数必须用async标识
  • await 后面跟的是一个Promise实例
  • 需要babel-polyfill

虚拟dom Virtual Dom

问题

  • v-dom 是什么?
  • v-dom 如何运用?
  • 介绍Diff算法

v-dom 是什么?

  • 用js模拟dom结构
  • 如果dom有变化,对比出变化的地方,只修改这个变化的地方
  • 提高重绘性能
  • JS是前端中唯一:图灵完备语言
  • 将DOM对比操作放在JS层,提高效率

遇到的问题

  • DOM操作是 "昂贵"的,js运行效率高
  • 尽量减少DOM操作,而不是 "推倒重来"
  • 项目越复杂,影响越严重
  • vdom即可解决这个问题

snabbdom

  • 实现vdom的开源库
  • vue2.0借用了snabbdom
  • h函数、patch函数
  • h函数返回v-node
  • patch函数将vnode patch到一个真实的dom容器中

Diff算法

问题

  • 什么是diff算法?
  • vdom为何用diff算法?
  • diff算法的实现过程

vdom为何使用diff算法

答案:

  • DOM操作是 "昂贵"的,因此尽量减少DOM操作
  • 找出本次DOM必须更新的节点来更新,其他的不更新
  • 这个 "找出" 的过程,就需要diff算法

diff算法的实现过程

  • patch(container, vnode)
  • patch(vnode, newVnode)

什么是diff算法?

  • 是linux的基础命令 diff git diff

{

tag: 'ul',

attrs: {

id: 'list'

},

children: [

{

tag: 'li',

attrs: {

className: 'item'

},

children: ['Item 1']

}

]

}

MVVM

问题

  • 如何理解MVVM?
  • 如何实现MVVM?
  • 是否解读过vue的源码?

题目

  • 说一下使用jQuery和使用框架的区别?
  • 说一下对MVVM的理解?
  • vue中如何实现响应式?
  • vue中如何解析模板?
  • vue的整个实现流程?

从jQuery到框架

  • 数据和视图的分离,解耦(开放封闭原则)
  • 以数据驱动视图,只关心数据变化,DOM操作被封装

如何理解 MVVM

  • MVVM - Model View ViewModel
  • 三者之间的联系,以及如何对应到各段代码
  • ViewModel的理解,联系View和Model

vue三要素

  • 响应式:vue如何监听到data的每个属性变化?
  • 模板引擎:vue的模板如何被解析,指令如何处理?
  • 渲染:vue的模板如何被渲染成html?以及渲染过程

vue中如何实现响应式

  • 什么是响应式
  • Object.defineProperty
  • 模拟

什么是响应式

  • 修改data属性之后,vue立刻监听到
  • data属性被代理到vm上

问题解答

  • 关键是理解 Object.defineProperty
  • 将data的属性代理到vm上

vue中如何解析模板

  • 模板是什么
  • render函数
  • render函数与vdom

模板是什么?

  • 本质:字符串
  • 有逻辑,如v-if v-for等
  • 与html格式很像,但有很大区别
  • 最终还要转换为html来显示
  • 模板最终必须转换成JS代码,因为:
  • 有逻辑(v-if v-for),必须用JS才能实现(图灵完备)
  • 转换为html渲染页面,必须用JS才能实现
  • 因此,模板最终要转换成一个JS函数(render函数)

render 函数 - with的用法


var obj = {

name: 'zhangsan',

age: 20,

getAddress: function () {

alert('beijing')

}

}

function fn() {

with(obj){

console.log(name)

console.log(age)

getAddress()

}

}
  • 从哪里可以看到render函数?
  • 复杂一点的例子,render函数是什么样子的?
  • v-if v-for v-on 都是怎么处理的?
  • vm._c其实就是snabbvdom的h

解答

  • 模板:字符串,有逻辑,嵌入JS变量.....
  • 模板必须转换为JS代码(有逻辑、渲染html、JS变量)
  • render函数是什么样子的
  • render函数执行是返回vnode
  • updateComponent
  • 模板中的所有信息都被render函数包含

vue的整个实现流程

  • 第一步:解析模板成render函数
  • 第二步:响应式开始监听
  • 第三步:首次渲染,显示页面,且绑定依赖
  1. 初次渲染,执行updateComponent,执行vm._render()
  2. 执行render函数,会访问到vm.list和vm.title
  3. 会被响应式的get方法监听到
  4. 执行updateComponent,会走到vdom的patch方法
  5. patch将vnode渲染成DOM,初次渲染完成
  6. 为何要监听get,直接监听set不行吗?
  7. data中有很多属性,有些被用到,有些可能不被用到
  8. 被用到的会走到get,没用到就不会走get
  • 第四步:data属性变化,触发re-render

组件化

题目

  • 说一下对组件化的理解
  • JSX本质是什么?
  • JSX和vdom的关系
  • 说一下setState的过程
  • 阐述自己对React和Vue的认识

说一下对组件化的理解

  • 组件的封装
  • 组件的复用

组件的封装

  • 视图
  • 数据
  • 变化逻辑(数据驱动视图变化)

组件的复用

  • props传递

解答

  • 组件的封装: 封装视图、数据、变化逻辑
  • 组件的复用: props传递、复用

JSX本质是什么?

  • JSX语法
  • JSX解析成JS
  • 独立的标准

JSX语法

  • html形式
  • 引入JS变量和表达式
  • if...else
  • 循环
  • style和className
  • 事件
  • JSX语法根本无法被浏览器所解析
  • 那么它如何在浏览器运行?

JSX 解析

  • JSX其实是语法糖
  • 开发环境会将JSX编译成JS代码
  • JSX的写法大大降低了学习成本和编码工作量
  • 同时,JSX也会增加debug成本

JSX独立的标准

  • JSX是React引入的,但不是React独有的
  • React已经将它作为一个独立标准开放,其他项目也可用
  • React.createElement 是可以自定义修改的
  • 说明:本身功能已经完备;和其他标准兼容和扩展性没问题
  • 另:有机会录制<<1000行代码实现React>>,就用JSX标准

问题解答

  • JSX语法(标签、JS表达式、判断、循环、事件绑定)
  • JSX是语法糖,需被解析成JS才能运行
  • JSX是独立的标准,可被其他项目使用

JSX和vdom的关系

  • 分析:为何需要vdom
  • React.createElement和h
  • 何时patch?
  • 自定义组件的解析

为何需要vdom

  • vdom 是 React初次推广开来的,结合JSX
  • JSX就是模板,最终要渲染成html
  • 初次渲染 + 修改 state 后的 re-render
  • 正好符号vdom的应用场景

React.createElement和h

  • 初次渲染 - ReactDOM.render(,container)
  • 会触发patch(container,vnode)
  • re-render - setState
  • 会触发patch(vnode,newVnode)

自定义组件的解析

  • 'div' - 直接渲染即可,vdom可以做到
  • Input和List,是自定义组件(class),vdom默认不认识
  • 因此Input和List定义的时候必须声明render函数
  • 根据props初始化实例,然后执行实例的render函数
  • render函数返回的还是vnode对象

问题解答

  • 为何需要vdom: JSX需要渲染成html,数据驱动视图
  • React.createElement和h,都生成vnode
  • 何时patch: ReactDOM.render(...) 和 setState
  • 自定义组件的解析:初始化实例,然后执行render

说一下 React setState 的过程

  • setState的异步
  • vue修改属性也是异步
  • setState的过程

setState的异步


addTitle(title) {

const currentList = this.state.list

console.log(this.state.list) // ['a', 'b']

this.setState({

list:currentList.concat(title) // 'c'

})

console.log(this.state.list) // ['a', 'b']

}

setState 为何需要异步?

  • 可能会一次执行多次 setState
  • 你无法规定、限制用户如何使用setState
  • 没必要每次setState 都重新渲染,考虑性能
  • 即便是每次重新渲染,用户也看不到中间的效果

vue修改属性也是异步

  • 效果、原因和setState一样

setState的过程

答案:

  • 每个组件实例,都有renderComponent方法
  • 执行renderComponent会重新执行实例的render
  • render函数返回newVnode,然后拿到preVnode
  • 执行patch(preVnode,newVnode)

问题解答

  • setState的异步:效果、原因
  • vue修改属性也是异步:效果、原因
  • setState的过程:最终走到patch(preVnode,newVnode)

React vs Vue

  • 两者的本质区别
  • 看模板和组件化的区别
  • 两者共同点
  • 总结问题答案

前言

  • 文无第一武无第二,技术选型没有绝对的对与错
  • 技术选型要考虑的因素非常多
  • 作为面试者,要有自己的主见
  • 和面试官的观点不一致没关系,只要能说出理由

两者的本质区别

  • vue - 本质是MVVM框架,由MVC发展而来
  • React - 本质是前端组件化框架,由后端组件化发展而来
  • 但这并不妨碍他们两者都能实现相同的功能

模板的区别

  • vue - 使用模板 (最初由 angular 提出)
  • React - 使用JSX
  • 模板语法上,我更加倾向于JSX
  • 模板分离上,我更加倾向于vue

组件化的区别

  • React本身就是组件化,没有组件化就不是React
  • vue也支持组件化,不过是在MVVM上的扩展
  • 查阅vue组件化的文档,洋洋洒洒很多(侧面反映)
  • 对于组件化,我更加倾向于React,更加清晰

两者共同点

  • 都支持组件化
  • 都是数据驱动视图

问题解答

  • 国内使用,首推vue。文档更易读、易学、社区够大
  • 如果团队水平较高,推荐使用React。组件化和JSX

hybrid

  • 移动端占大部分流量,已经远远超过PC
  • 一线互联网公司都有自己的App
  • 这些App中有很大比例的前端代码(不要惊讶)
  • 拿微信举例,你每天浏览微信的内容,多少是前端?

题目

  • hybrid是什么,为何用hybrid?
  • 介绍一下hybrid更新和上线的流程?
  • hybrid和h5的主要区别
  • 前端JS和客户端如何通讯?

hybrid是什么,为何会用hybrid?

  • hybrid 文字解释
  • 存在价值,为何会用hybrid
  • webview
  • file:// 协议
  • hybrid实现流程

hybrid 文字解释

  • hybrid 即 "混合",即前端和客户端的混合开发
  • 需前端开发人员和客户端开发人员配合完成
  • 某些环节也可能涉及到server端
  • PS:不要以为自己是前端就可以不理会客户端的知识

hybrid存在价值

  • 可以快速迭代更新「关键」(无需app审核,思考为何?)
  • 体验流畅(和NA的体验基本类似)
  • 减少开发和沟通成本,双端公用一套代码

webview

  • 是app中的一个组件(app 可以有webview,也可以没有)
  • 用于加载H5页面,即一个小型的浏览器内核

file:// 协议

  • 其实在一开始接触html开发,就已经使用了file协议
  • 只不过你当时没有 "协议" "标准"等这些概念
  • 再次强调 "协议" "标准"的重要性!!!
  • file协议: 本地文件,快
  • http(s)协议: 网络加载,慢

hrbrid 具体实现

  • 不是所有场景都适合使用hybrid:
  • 使用NA: 体验要求极致,变化不频繁 (如头条的首页)
  • 使用hybrid: 体验要求高,变化频繁 (如头条的新闻详情页)
  • 使用h5: 体验无要求, 不常用 (如举报、反馈等页面)

实现

  • 前端做好静态页面 (html js css), 将文件交给客户端
  • 客户端拿到前端静态页面,以文件形式存储在app中
  • 客户端在一个webview中,通过file协议加载前端文件(html js css)

具体实现 - 遗留问题

  • app发布之后,静态文件如何实时更新?
  • 静态页面如何获取内容?

问题解答

  • hybrid 是客户端和前端的混合开发
  • hybrid 存在的核心意义在于快速迭代,无需审核
  • hybrid实现流程(图),以及webview和file协议

hybrid 更新上线流程

思考 (目的,可行途径)

  • 要替换每个客户端的静态文件
  • 只能客户端来做 (客户端是我们开发的)
  • 客户端去server下载最新的静态文件
  • 我们维护server的静态文件

完整流程

  • 分版本,有版本号, 如201803211015
  • 将静态文件压缩成zip包,上传到服务端
  • 客户端每次启动,都去服务端检查版本号
  • 如果服务端版本号大于客户端版本号,就去下载最新的zip包
  • 下载完之后解压包,然后将现有文件覆盖

问题解答

  • 掌握流程图
  • 要点1:服务端的版本和zip包维护
  • 要点2:更新zip包之前,先对比版本号
  • 要点3:zip下载解压和覆盖

hybrid和h5的比较

优点

  • 体验更好,跟NA体验基本一致
  • 可快速迭代,无需审核

缺点

  • 开发成本高。联调、测试、查bug都比较麻烦
  • 运维成本高。参考此前讲过的更新上线的流程

适用的场景

  • hybrid: 产品的稳定功能,体验要求高,迭代频繁
  • h5: 单次的运营活动(如 xx 红包) 或不常用功能

问题解答

  • 优点:体验好,可快速迭代
  • 缺点:开发成本高,运维成本高
  • 适用的场景:hybrid适合产品型,h5适合运营型

JS和客户端通讯

  • 回顾之前遗留的问题
  • JS和客户端通讯的基本形式
  • schema协议简介和使用
  • schema使用的封装
  • 内置上线

之前遗留的问题

  • 新闻详情页适用hybrid,前端如何获取新闻内容?
  • 不能用ajax获取。第一 跨域,第二 速度慢
  • 客户端获取新闻内容,然后JS通讯拿到内容,再渲染

JS和客户端通讯的基本形式

  • JS访问客户端能力,传递参数和回调函数
  • 客户端通过回调函数返回内容

schema 协议简介和使用

  • 之前介绍了http(s) 和 file协议
  • schema协议 - 前端和客户端通讯的约定

/* 网上搜的微信的部分 schema 协议 */

weixin://dl/scan 扫一扫

weixin://dl/feedback 反馈

weixin://dl/moments 朋友圈

weixin://dl/settings 设置

内置上线

  • 将以上封装的代码打包,叫做invoke.js 内置到客户端
  • 客户端每次启动webview,都默认执行invoke.js
  • 本地加载,免去网络加载的时间,更快。
  • 本地加载,没有网络请求,黑客看不到schema协议,更安全

问题解答

  • 通讯的基本形式:调用能力,传递参数,监听回调
  • 对schema协议的理解和使用
  • 调用schema代码的封装
  • 内置上线的好处:更快、更安全

如何热爱编程?

  • 热爱!
  • 怎么证明?

如何证明你热爱编程?

  • 看书
  • 写博客
  • 做开源

看书 - 手下不离书

  • 构建知识体系的最好方式
  • 自己买书,不要借书
  • 看书有技巧

博客 - 合格程序员的必备

开源 - github 的 star 是硬通货

Subscribe to MarkTang's Blog
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.