面试题(高级篇)

  1. React组件销毁中清理异步操作和取消请求

通常是 react 组件已经从 DOM 中移除,但是我们在组件中做的一些异步操作还未结束,如:接口调用等,当其完成时,执行setState操作,而此时我们已经将改组件dom移除,从而报错。

componentWillUnmount() {
     
  this.setState = (state, callback) => {
     
    return
  }
}
  1. react16新特性
  2. react 16 hooks
  3. http1.0 与http2.0的区别
  4. H5移动端开发技巧
  5. event-loop(事件轮询):JS实现异步的具体解决方案

具体步骤如下:

  1. 同步代码,直接执行
  2. 异步函数先放在异步队列中
  3. 待同步函数执行完毕,轮询执行异步队列的函数。
// // 异步函数放在异步队列中,待同步代码执行完毕后执行
$.ajax({
     
	url: '***',	// success成功后放入异步队列中
	success: function(result) {
		console.log('a')
	}
})
setTimeout(function() {
     
	console.log('b')	// 100ms后放入异步队列中
}, 100)
setTimeout(function() {
     
	console.log('c')	// 立即放入异步队列中
})
console.log('d')	//同步代码,直接执行
// 执行结果为:d, c, (ajax的success时间<100 ? a,b : b,a)
  1. webpack打包优化
  2. webpack性能监控与分析
  3. fiddler抓包工具
  4. react性能优化
  5. 常见的移动端兼容性问题
  6. react特点
    1. 声明式设计-------采用声明规范,用ReactDOM.render()来声明
    2. 采用虚拟DOM的模拟,最大限度的减少和真实DOM节点的交互
    3. 灵活—使用库和框架
    4. 组件—组件的数据流之间的传递是单向的
    5. 单向响应的数据流----使用setState去更改数据的值,使用getInitialState设置值,用this.state来获取值
  7. 浏览器缓存
  8. 异步编程六种方式
  9. 继承
  10. ES6模块化如何使用,开发环境如何打包?

ES6是javascript的一个标准(可以理解为他是原生javascript的一个版本号),目前每个浏览器对ES6的支持程度不一样,所以需要使用babel对ES6进行编译。
语法:import export(注意有无default)
环境:babel编译ES6语法(创建.babelrc文件),模块化可用webpack和rollup打包

// util1.js
export default {
      a: 100}
// util2.js
export function fn1() {
     
	alert('fn1')
}
export function fn2() {
     
	alert('fn2')
}
// index.js
import util1 from './util1'	// export default的导出不需要使用{}
import {
     fn1, fn2} from './util2'
// .babelrc文件
{
     
	"presets": [
		["latest", {
     
			"es2015": {
     
				"modules": false	// 编译时不关心第三方插件的引入
			}
		}]
	],
	"plugins": ["external-helpers"]
}

// webpack.config.js配置
module.exports = {
     
	entry: './src/index.js',	// 文件入口
	output: {
     	// 在当前目录下创建build/bundle.js文件,并将编译后的数据放在里面
		path: _dirname,
		filename: './build/bundle.js'
	},
	module: {
     	// 使用babel-loader编译所有的js文件,node_modules目录除外
		rules: [{
     
			test: /\.js?$/,
			exclude: /(node_modules)/,
			loader: 'babel-loader'
		}]
	}
}
// rollup比较小
// rollup功能单一,webpack功能强大
// 工具要尽量单一,可集成,可扩展
// 可以使用gulp+rollup进行打包
import babel from 'rollup-plugin-babel'
import resolve from 'rollup-plugin-node-resove'
export default {
     
	extry: 'src/index.js',
	format: 'umd',
	plugins: [
		resolve(),
		babel({
     
			exclude: 'node_modules/**'
		})
	],
	dest: 'build/bundle.js'
}

模块化标准有:AMD(require.js),CMD(c.js),Common(node.js)
nodeJS使用的是Common标准,前端打包工具可以使用。

  1. Class和普通构造函数有何区别?

class在语法上更加贴合面向对象的写法
class实现继承更加易读、易理解
本质还是语法糖,使用prototype(可将class看作是一个语法糖)

// JS构造函数
function MathHandle(x,y) {
     
	this.x = x;
	this.y = y;
}
// 构造函数原型上添加方法,实现共享
MathHandle.prototype.add = function() {
     
	return this.x + this.y;
}
// 实例化
var m = new MathHandle(1,2);
console.log(m.add())
// class语法
class MathHandle {
     
	// 构造函数
	constructor(x,y) {
     
		this.x = x;
		this.y = y;
	}
	// 	方法
	add() {
     
		return this.x + this.y
	}
}
// 实例化
var m = new MathHandle(1,2);
console.log(m.add())	// 3
typeof 	MathHandle      // 值为function
MathHandle = MathHandle.prototype.constructor       // true
m.__proto__ === MathHandle.prototype			// true
// JS继承
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()	// hashiqi具有Dog和Animal上的方法
// class实现继承
class Animal {
     
	construtor(name) {
     
		this.name = name
	}
	eat() {
     
		console.log(`${
       this.name} eat`)
	}
}
// 继承
class Dog extends Animal {
     
	constructor(name) {
     
		super(name)	// 必须写,用于继承Animal的属性
		this.name = name
	}
	say() {
     
		console.log(`${
       this.name} say`)
	}
}
const dog = new Dog('哈士奇')
dog.say()
dog.eat()
  1. 不使用prototype和new实现继承
  2. promise

new Promise实例,而且要return 该实例
new Promise时要传入函数,函数有resolve reject两个参数
成功时执行resolve(), 失败时执行reject()
使用.then监听结果

function loadImg(src, callback, fail) {
     
	var img = document.createElement('img')
	img.onload = function() {
     
		callback(img)
	}
	img.onerror = function() {
     
		fail()
	}
	img.src = src
}
var src = 'http://www.baidu.com/static/img/index/logo.png'
loadImg(src, function(img) {
	console.log(img.width)
}, function() {
     
	console.log('failed')
})
// 使用promise改造上面的callback函数
function loadImg(src) {
     
	const promise = new Promise(function(resolve, reject) {
     
		var img = document.createElement('img')
		img.onload = function() {
     
			resolve(img)
		}
		img.onerror = function() {
     
			reject()
		}
		img.src = src 
	})
	return promise
}
var src = 'http://www.baidu.com/static/img/index/logo.png'
var result = loadImg(src)
result.then(function(img) {
	console.log(img.width)
}, function() {
     
	console.log('failed')
})
// then只接受一个成功的函数,最后使用catch统一进行错误捕获
result.then(function(img) {
	console.log(img.height)
}).then(function(img) {
     
	console.log(img.src)
}).catch(function(ex) {
     
// catch不仅可以捕获自定义的语法错误,也会捕获reject错误
	console.log(ex)
})
// 串联promise
var src1 = 'https://www.baidu.com/static/img/index/logo.png'
var result1 = loadImg(src1)
var src2 = 'https://www.baidu.com/static/img/index/logo2.png'
var result2 = loadImg(src2)
result1.then(function(img1) {
     
	console.log('第一张图片加载完成')
	return result2	// 若不return 引用的还是result1
}).then(function(img2) {
     
	console.log('第二个图片加载完成')
}).catch(function(ex) {
     
	console.log(ex)
})
// promise.all接收一个promise对象的数组
// 待全部完成之后,统一执行success
Promise.all([result1, result2]).then(datas = {
     
	// datas是一个数组依次包含多个promise返回的内容
	console.log(datas[0], datas[1])
})

// Promise.race接收一个包含多个Promise对象的数组
// 只要有一个完成,就执行success
Promise.race([result1, result2]).then(data => {
     
	console.log(data)	// data最先执行完成的promise的返回值
})
  1. ES6常用功能?

模块化(import export)、class、promise、let/const、多行字符串/模板变量、解构赋值、块级作用域、函数默认参数、箭头函数

// let/const: const是常量,let是变量
// 多行字符串/模板变量
const el = `${
       this.name} age`
// 解构赋值
const obj = {
     a: 10, b:20, c:30}
const {
     a,c} = obj
const arr = [10,20,30]
const [a,b,c] = arr
// 块级作用域
var obj = {
     a:100, b:200}
for(var item in obj) {
     console.log(item)}
console.log(item)	// b,定义的是全局的,内存外泄
for(let item in obj) {
     console.log(item)}
console.log(item)	// undefined
// 函数默认参数
function(a, b=0){
     }
// 箭头函数
function fn() {
     
	console.log('real', this)	// {a:100}
	var arr = [1,2,3]
	// 普通JS
	arr.map(function(item) {
     
		console.log('js', this)	// window对象
	})
	// 箭头函数
	arr.map(item => {
     
		console.log('es6', this)	// {a:100}
	})
}
fn.call({
     a: 100})
  1. 原型的实际应用:jquery与zepto
// jquery的使用
<script>
	var $p = $('p')
	$p.css('font-size', '40px')		// css是原型
	console.log($p.html())		// html是原型
	
	var $div = $('div')
	$div.css('color', 'red')		// css是原型
	console.log($div.html())		// html是原型
</script>

// zepto如何使用原型
var zepto = {
     }
// 即使用zepto时候的$
var $ = function(selector) {
     
	return zepto.init(selector)
}
zepto.init = function(selector)var slice = Array.prototype.slice
	var dom = slice.call(document.querySelectorAll(selector))
	return zepto.Z(dom, selector)
}
// 这就是构造函数
function Z(dom, selector) {
     
	var i, len = dom ? dom.length : 0
	for (i=0; i<len; i++) this[i] = dom[i]
	this.length = len
	this.selector = selector || ''
}
zepto.Z = function(dom, selector) {
     
	return new Z(dom, selector)
}
$.fn = {
     
	constructor: zepto.Z,
	css: function(key, value) {
     },
	html: function(value) {
     }
}
// 上面为什么不写为z.prototype,而是使用$.fn来中间转化下,为了实现插件扩展
zepto.Z.prototype = Z.prototype = $.fn
// jquery如何使用原型
var jQuery = function(selector) {
     
	return new jQuery.fn.init(selector);
}
// 构造函数
var init = jQuery.fn.init = function(selector) {
     
	var slice = Array.prototype.slice
	var dom = slice.call(document.querySelectorAll(selector))
	var i, len=dom ? dom.length : 0
	for(i=0; i<len; i++) this[i] = dom[i]
	this.length = len
	this.selector = selector || ''
}
// 初始化
jQuery.fn = jQuery.prototype = {
     
	constructor: jQuery,
	css: function(key, value) {
     }
	html: function(value) {
     }
}
init.prototype = jQuery.fn
  1. 原型如何体现它的扩展性:jquery和zepto的插件机制

好处:

  1. 只有$会暴露在window全局变量
  2. 将插件扩展统一到$.fn.xxx这一接口,方便使用
// 上面的例子中为什么要将原型方法放在$.fn中
// 为了扩展插件
$.fn.getNodeName = function() {
     
	return this[0].nodeName
}
  1. 什么是单线程,和异步有什么关系

    单线程:同时只做一件事,两段JS不能同时执行
    原因:避免DOM渲染冲突

    1. 浏览器需要渲染DOM
    2. JS可以修改DOM结构
    3. JS执行的时候,浏览器DOM渲染会暂停
    4. 两段JS也不能同时执行(都修改DOM就冲突了)
    5. webwork支持多线程 ,但是不能访问DOM
  2. 当前异步的解决方案?
  1. callback
  2. jquery的deferred’
  3. promise
  4. async/await
  5. generator
  1. vdom是什么,为何会存在vdom?

vdom即虚拟DOM,用JS模拟DOM结构,将DOM对比操作放在JS层提高效率
目的:提高重绘性能(减少对DOM的操作与重绘,提高运行效率)。

// 没有vdom时,DOM操作(反复操作DOM,浪费性能)
<body>
  <div id="container"></div>
  <button id='btn-change'>change</button> 
</body>
<script type='text/javascript'>
  var data = [
    {
     name: '张三', age: 20, address: 'bj'}, 
    {
     name: '李四', age: 32, address: 'sh'}
  ]
  // 渲染函数
  function render(data) {
     
    var $container = $('#container')
    // 清空container, 每次render的时候会重新渲染
    $container.html('') 
    // 拼接元素
    var $table = $('');
    $table.append($(''));
    data.forEach(({
     name, age, address})=>{
     
      $table.append($(``))});// 渲染到页面
    $container.append($table)}$('#btn-change').click(function(){
     
    data[1].age =30
    data[0].address ='hb'render(data)// 数据变化,再次重绘DOM})render(data)// 页面加载完立即执行,初次渲染</script>
  1. vdom如何使用,核心API是什么 ?

vue1.0使用的snabbdom开源库实现的vdom

// 真实dom结构
<ul id='list'>
	<li calss='item'>Item1</li>
	<li calss='item'>Item2</li>
</ul>
// JS模拟DOM结构
{
     
	tag: 'ul',
	attrs: {
     id: 'list'},
	children: [{
     
		tag: 'li',
		attrs: {
     className: 'item'},
		children: ['Item1']
	},{
     
		tag: 'li',
		attrs: {
     className: 'item'},
		children: ['Item2']
	}]
}
// snabbdom主要提供了两个方法:
// h(虚拟dom节点)
// patch(container, vnode): dom首次进行渲染
// patch(vnode, newvnode): dom对比并进行渲染

面试题(高级篇)_第1张图片
27. 什么是diff算法?vdom为何用diff算法?
28. diff算法的实现流程

// 实现patch(vnode, newVnode): vnode的数据结构参见上面的JS DOM结构
function updateChildren(vnode, newVnode) {
     
	var children = vnode.children || []
	var newChildren = newVnode.children || []
	// 遍历现有的children
	children.forEach(function(child, index) {
     
		var newChild = newChildren[index]
		if(newChild == null) return
		// 虚拟DOM节点会有一个唯一KEY,可以用来比较
		if(child.tag === newChild.tag) {
     
			// 两者tag一样,递归遍历子集
			updateChildren(child, newChild)
		} else {
     
			// 两者不一样,新的替换旧的
			replaceNode(child, newChild)
		}
	})
}
function replaceNode(vnode,newVnode) {
     
	var elem = vnode.elem	// 真实的DOM节点
	var newEleme = createElement(newVnode)
	替换
  1. JSbridge
  2. call、apply、bind的区别?
  3. webpack打包文档
  4. 使用jquery和使用框架的区别?
    1. 数据和视图的分离;
    2. 以数据驱动视图(只关心数据变化,DOM操作被封装)
  5. 如何理解MVVM?

MVC:(modal数据源,view视图,controller控制器)
运行流程:view上的一个操作(如点击等)通知controller,controller操作modal,modal发生变化,更新view

MVVM: (modal数据,view视图,viewModal-连接modal和view)
viewModal工作:view通过事件绑定操作modal,modal通过数据绑定操作view

面试题(高级篇)_第2张图片
34. VUE三要素

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

  1. vue中如何实现响应式?

什么是响应式?
修改data属性之后,vue立刻监听到
data属性被代理到vm上

// 响应式的data属性监听使用的object.defineProperty
// Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性
// Object.defineProperty(obj需要定义属性的当前对象, prop前需要定义的属性名, desc属性描述符)
var mv = {
     }
var data = {
     name: 'lmh', age: 18}
var key, value
for(key in data {
     
	// 闭包新建一个函数,保证key的独立的作用域
	(function (key) {
     
		Object.defineProperty(mv, key, {
     
			get: function() {
     	// 监听mv的属性(获取)
				return data[key]
			},
			set: function(newVal) {
     	// 监听mv的属性修改
				data[key] = newVal
			}
		})
	})(key)
}

// data属性被代理到vm上:使用with
var obj = {
     name: 'lmh', age: 18, getAddress: function() {
     alert('bj')}}
function fn() {
     
	with(obj){
     	// 不推荐使用with
		alert(name)	// lmh
		alert(age)	// 18
		getAddress()	// bj
	}
}
  1. vue实现的整体流程

第一步:解析模板成render函数
第二步:响应式开始监听
第三步:首次渲染,显示页面,且绑定依赖(v-if, on:click等)
第四步:data属性变化,触发reRender

  1. 说一下对组件化的理解

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

  2. JSX的本质是什么(语法糖)
    面试题(高级篇)_第3张图片
  3. 自定义组件的解析

'div’直接渲染

即可
List等自定义组件(class),vdom默认不认识
因此List定义的时候必须声明render函数
根据props初始化实例,然后执行实例的render函数
render函数返回的还是vnode对象

ReactDOM.render(<App/>, document.getElementById('root'))

// App自定义函数的解析
React.createElement(App, null);
var app = new App()
return app.render()
  1. 说一下setState的过程

setState是异步的
每个组件实例,都有renderComponent方法
执行renderComponent会重新执行实例的render
render函数返回newVnode,然后重新preVnode
执行path(preVnode, newVnode)

add(title) {
     
	const currentList = this.state.list
	console.log(this.state.list)	// ['a', 'b']
	this.setState({
     list: currentList.concat(title)})
	// 值依然是['a','b'], 因为setState是异步的
	console.log(this.state.list)
	// 解决异步使用callback
	this.setState(
		{
     list: current.concat(title)},
		() => {
     consol.log(this.state.list))	// ['a','b','c']
}
  1. setState异步原理
    面试题(高级篇)_第4张图片
  2. 阐述一下自己对React和Vue的认识
  1. 两者的本质区别
    vue-本质是MVVM框架,由MVC发展而来
    React-本质是前端组件化框架,由后端组件化发展而来
    注:但这并不妨碍他们两者都能实现相同的功能
  2. 模板的区别
    vue-使用模板(最初由angular提出)
    React-使用JSX
    模板语法上,我更加倾向于JSX
    模板分离上,我更加倾向于vue
  3. 组件化的区别
    react本身就是组件化,没有组件化就不是react
    vue也支持组件化,不过是在MVVM上的扩展
    对于组件化,更加倾向于React,做的彻底而清晰
  4. 两者的共同点
    都支持组件化
    都是数据驱动视图
  5. 两者对比
    国内使用,首推vue。文档更易读、易学、社区够大
    如果团队水平较高,推荐使用React。组件化和JSX
// vue模板
<div>
	<h1 v-if='ok'>yes</h1>
	<h1 v-else>no</li>
</div>
// react模板
<div> 
	{
     ok ? <h1>yes</h1> : <h1>no</h1>}
</div>

// 模板和JS混在一起,未分离
// vue的模板和JS逻辑是分离的
render() {
     
	return (
		<div>
			<Input addTitle={
     this.addTitle.bind(this)} />
			<List data={
     this.state.list} />
		</div>
	)
}
  1. hybrid是什么,为什么要用hybrid?
  1. hybrid即前端和客户端的混合开发
  2. 存在价值
    可以快速迭代更新(无需app审核)
    体验流畅(和NA的体验基本类似)
    减少开发和沟通成本,双端公用一套代码
  3. 缺点
    开发成本高。联调、测试、查BUG都比较麻烦
    运维成本高。(需要维护APP和H5两段,每次H5更新时都要进行版本号查询对比,以确认最新版)
  4. 适应场景
    hybrid:产品的稳定功能,体验要求高,迭代频繁
    h5:单次的运营活动(如**红包)或不常用功能
  1. webview

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

45.如何证明你热爱编程?

看书
写博客
做开源

你可能感兴趣的:(笔记整理,面试)

nameageaddress
${ name}${ age}${ address}