四万字 | web前端面试大纲整理

更新中,以下整理于CSDN、掘金等博客以及b站视频

web前端

HTML

1、HTML5新特性

语义化标签类
音视频处理
canvas/webGL
history API
requestAnimationFrame
地理位置
web scoket

2、基础知识

响应式布局开发方案:@media/rem/flex
viewport和dpi适配
H5音视频处理方案
新表单元素和表单权限
CSS预编译语言:less/sass

3、canvas和echarts

canvas的基础用法
canvas动画及小游戏
canvas图像处理
echarts基础API
多元化/个性化数据图表练习
异步数据的处理和更新
echarts的事件和行为

CSS

新属性
动画
盒模型
display
position
响应式布局
实现水平垂直居中
浏览器内核/兼容性
隐藏的几种方法/区别
BFC

BFC,即Box(CSS布局的对象和基本单位)

标签选择器/优先级
布局类型

JS

1、堆栈内存及闭包作用域

JS中的8种数据类型及区别
  • 基本类型:string number boolean null undefined Symbol(es6,表示唯一标识符,同一变量生成的值也不相同)BigInt(新增,末尾加n,与普通数字“”为true,严格模式“=”为false)
    引用类型:function(_photo_Function.prototype)
    object(普通对象,数组对象,正则对象,日期对象,Math数学函数对象)

  • 判断类型:
    typeof string number boolean undefined bigInt sysmbol function 返回各自类型(返回字符串)
    null object array 和函数实例(new+函数)返回object
    instanceof判断该对象是谁的实例,obj1 instanceof obj2,(返回布尔值)
    constructor检测构造函数
    Object.prototype.toString.call检测数据类型

<!--基本类型-->
console.log(typeof(33))//number
console.log(typeof('aa'))//string
console.log(typeof(null))//object
console.log(typeof(undefined))//undefined
console.log(typeof(true))//boolean
console.log(typeof(BigInt(33333333333333333333333333)))//bidInt
console.log(typeof(33333333333333333333333333333333333n))//bidInt
console.log(typeof(Symbol(3)))//sysmbol

<!--引用类型-->
console.log(typeof([1,2,3]))//object
console.log(typeof({'name':'aaa'})//object
console.log(typeof(function a(){}))//function

console.log([] instanceof Array)//true
console.log([] instanceof Object)//true数组也是一种对象
JS数组方法

详见 JS中的数组方法

JS堆栈内存的运行机制
  • js的内存生命周期
    (1)定义变量时完成了分配内存
    (2)使用值的过程实际上是对分配内存进行读取和写入的操作,
    (3)内存释放依赖GC机制(高级语言解释器嵌入“垃圾回收器”)
    程序运行的时候,需要内存存放数据
    系统会划分出两种不同的空间:堆(heap)和栈(stack)

  • V8是使用最广泛的JavaScript引擎,它被 使用在node.js和chrome浏览器中
    这个引擎包含两个组件
    (1)内存堆——这个是内存分配发生的地方
    (2)调用堆栈——这是JS代码执行的数据帧所在的地方

  • 基本数据类型的值会存储在当前作用域下
    var a=12;
    1)首先开辟一个空间,存储12
    2)在当前作用域中声明一个变量a
    3)让声明的变量和存储的a进行关联,把存储的12赋值给a(定义)
    基本数据类型是按照值来操作的,把原有的值复制一份放在新的空间或位置上,和原来的值没有关系;

  • 引用数据类型要先开辟一个空间
    var obj={n:10}
    1)首先开辟一个新的内存空间,把对象中的键值对依次存储起来,这个空间有一个16进制地址;
    2)声明一个变量
    3)让变量和空间地址关联在一起(把空间地址赋值给变量)
    引用类型不是按照值来操作的,它操作的是空间的引用地址,把原来空间地址赋值给新的变量,但是原来的空间没有被克隆,还是一个空间,遮掩就会出现多个变量关联相同的空间,相互之间会存在影响;
    函数被调用时,js引擎会为全局执行上下文和调用栈腾出空间

  • 调用堆栈
    JavaScript是一种单线程编程语言,它只有一个Call Stack(调用堆栈)
    (1)所有同步任务都在主线程上执行,形成一个执行栈;
    (2)主线程之外,还存在“任务队列”,只要有异步任务有了运行结果,就在“任务队列”之中放置一个事件。
    (3)一旦“执行栈”中所有的同步任务执行完毕,系统就会读取“任务队列”,对应的异步任务,结束等待状态,进入执行栈,开始执行。
    (4)主线程不断重复上面的第三步

即:调用栈中的同步任务被执行完,栈就被清空,主线程栈空闲,就会去任务队列中按顺读取下一个任务放到栈中执行,每次栈内被清空,就会去读取任务队列中有没有任务,有就读取执行,一直循环读取-执行操作。=>event-loop(一个程序结构,用于等待和发送消息和事件)

  • Event-Loop
    JavaScript是单线程的,因此所有任务都要排队,前一个任务结束,才会执行后一个任务。如果一个任务耗时很长,后一个任务就不得不一直等着,因此用户代理必须使用事件循环**=>为什么使用event-loop**

所有任务分成两类:
同步任务(aynchronous):在主线程上排队执行的任务,前一个任务结束,才执行后一个任务;
异步任务(asynchronous):不进入主线程,而进入“任务队列”,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

异步任务又分两类
宏任务(macrotask):script(整体代码),setTimeout,setInterval,setImmediate(node独有),requestAnimationFrame(浏览器独有),I/O ,UI rendering(浏览器独有);
微任务(microtask):process.nextTick(node独有), Promise ,Object.observe , MutationObserve

同一次事件循环中,微任务永远在宏任务之前进行

变量提升机制
  • js执行过程中,把变量和函数的声明部分提升到代码开头,给变量设置默认值undefined;
  • 私有域作用域
  console.log(a,b);//undefined undefined
    var a = 12,b = 12;
    function(){
      console.log(a,b);//undefined 12
      var a = b = 13;//相当于a声明并赋值,b只是赋值
      console.log(a,b);//13 13
    }
    fn();
    console.log(a,b);//12 13
第一行,声明a,b,fn,因此输出undefined (已声明,未赋值);
第二行,为a,b赋值
第三行,已经声明和定义函数,调至第8行
第八行,开始建立局部作用域,回到第四行
第四行,变量提升a,执行输出,此时a未赋值,是undefined,b在函数内未声明,向上级作用域找,如果一直不是,则继续向上直到window**=>作用域链**
第五行,给局部变量和全局变量赋值13
第九行,此时a和b都是全局变量,a未改变,但是b的值被改为13
  • 条件判断时,无论条件是否满足,变量仍会提前
  console.log(a);//undefined
    if(‘a’ in window){
      var a= 12;
      console.log(a);//12
    }
  • es6中let创建的变量不存在变量提升
作用域和作用域链
  • 作用域
    (1)作用域就是一个独立的地盘,让变量不会外泄、暴露出去,也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突;
    (2)ES6之前JavaScript没有块级作用域,只有全局作用域和函数作用域,ES6中使用let和const来实现;
    (3)函数内部可以使用全局变量,函数外部不可以使用局部变量,当函数执行完毕,本作用域内部变量会销毁;
    —全局作用域
    在代码任何地方都能访问到的对象拥有全局作用域:最外层函数、所有未定义直接赋值的变量、所有window对象的属性

(3)作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行;
(4)块语句指的是大括号“{}”之间的,if/switch条件语句或for/while循环不会创建一个新的作用域;
(5)块级作用域:通过新增命令let / const声明,所声明的变量在指定块的作用域外无法被访问;在一个函数内部/在一个代码块(一对花括号内部)=>不会变量提升/禁止重复声明/for循环中妙用

  • 作用域链
    当前作用域中没有自由变量时,向父级作用域**(创建这个函数的域)**一层一层寻找,一层层的关系叫做作用域链
创建函数的几种方式
  • (1)声明函数(具名函数)
function fn1(){}
  • (2)函数表达式(匿名函数)
    创建一个变量,这个变量的内容为一个函数
    所有声明的匿名函数都是一个新函数
var fn1=function(){}
  • (3)函数表达式(具名函数)
    创建一个变量,内容为一个带有名称的函数
    具名表达式只能在创建函数内部使用
var fn1=function aaa(){}
  • (4)Function构造函数,所有的函数都是Function的实例
    可以给Function构造函数传一个函数字符串,返回包含这个字符串命令的函数,此种方法创建的是匿名函数
    必须加引号
var f=new Function('参数1','参数2',……,'函数体')
  • (5)自执行函数
(function(){})();
(function fn1(){})();
  • (6)对象里的函数调用,对象.函数名()
var obj={
	func1:function(){
		console.log('aaa');
	}
}
obj.func1();
  • (7)构造函数
function Star(){};
new Star();
  • (8)绑定事件函数
btn.onclick=function(){}
  • (9)其他创建函数的方法
    其他创建函数的方法,比如eval,setTimeout,setInvertal
setInterval(function(){},1000)
闭包的两大作用:保护/保存
  • 闭包是指有权访问另一个函数作用域中的变量的函数=>一个作用域可以访问另一个函数的局部变量

闭包是典型的高阶函数

  • 作用:(1)获取函数内部的变量(延伸了变量的作用范围);(2)让这个变量的值始终保持在内存中(不会被垃圾回收机制回收)

  • 注意:(1)闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄漏。解决办法:在退出函数之前,将不使用的局部变量全部删除;
    (2)闭包会在父函数内部,改变父函数内部变量的值。所以,如果把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作私有属性,一定要细心

function fn(){
	var num=10;
	function fun(){
		console.log(num);
	}
	return fun;
}
var f=fn();
f();
  • 应用

(1)循环注册点击事件

//点击一系列li标签的index顺序
var lis=document.querySelector(".nav").querySelector('li');
//1、利用动态添加属性
for(var i=0;i<lis.length;i++){
	lis[i].index=i;
	lis[i].onclick=function(){
		console.log(this.index);//注意不能直接打印i,会一直输出4
}
}
//2、利用闭包
for(var i=0;i<lis.length;i++){
	//立即函数也成为小闭包
	(function(j){
		lis[j].onclick=function(){
		console.log(j);
}
	})(i);

}

(2)循环中的setTimeout

//3秒中打印li中元素
var lis=document.querySelector(".nav").querySelector('li');
for(var i=0;i<lis.length;i++){
	setTimeout(function(){
		console.log(i);//3秒后打印4
		console.log(lis[i].innerHTML);//报错,定时器中的任务是异步任务,最后i变成4,li中没有lis[4]
	})
}

//小闭包

for(var i=0;i<lis.length;i++){
	(function(i){
		setTimeout(function(){
			console.log(lis[i].innerHTML);
		})
	})(i)
	
}

(3)计算打车价格

//起步13(3公里),之后每多一公里增加5块
//有拥堵情况,总价格多收取10元
var car=(function(){
	var start=13;//起步价
	var total=0;//总价
	return{
		price:function(n){
			if(n<=3){
				totla=13;
			}else{
				total=start+(n-3)*5
			}
			return total;
		},
		block:function(flag){
			return flag?total+10:total;
		}
	}

})
car.price(9);
car.block(true);
  • 思考题
var name="the window";
var object={
	name:"my object",
	getNamefunc:function(){
		return function(){
			return this.name;
		}
	}	
}
console.log(object.getNamefunc()())//the window
//=>相当于
var fn=object.getNamefunc();
//即
var fn=function(){
			return this.name;
		}
fn();//此时调用fn的是window,this指向window,没有用到闭包
var name="the window";
var object={
	name:"my object",
	getNamefunc:function(){
		var that=this;
		return function(){
			return that.name;
	}	
}
console.log(object.getNamefunc()())//my object
//=>相当于
var fn=object.getNamefunc();//object调用函数getNamefunc(),先执行了var that=this(此时this指向object,赋给了that)
//即
var fn=function(){
			return that.name;
		}
fn();//此时有闭包产生
垃圾回收机制
  • 栈内存(执行上下文)
    一般情况下,函数执行完,所形成的上下文被出栈释放掉
    特殊情况下:当前上下文中的某些内容被上下文以外的事物占用了,此时不能出栈释放;
    全局上下文:加载页面创建的,也只是有页面关闭才会被释放

  • 堆内存
    引用计数(以IE为主):在某些情况下回导致计数混乱,这样会造成内存不能被释放掉(内存泄漏);
    检测引用(以谷歌为主):浏览器在空闲时候会依次检测所有的堆内存,把没有被任何事物占用的内存释放掉,以此来优化内存;

JS编译机制:VO/AO/GO;

每一个上下文代码执行的时候,可能都会创建变量,所以在每一个上下文中都会有一个存储变量的空间VO
变量对象:存放当前上下文的变量
全局称为VO
私有上下文中称为AO

JS高阶编程技巧:惰性函数/函数柯里化/高阶函数;
惰性函数
  • 惰性函数表示函数执行的分支只会在函数第一次调用的时候执行。
    如果一个函数需要判断场景去调用不同方法,可以避免重复进入函数内的if判断,也就是if判断只进行一次,之后函数就会被分支里的代码替换掉。

  • 应用:
    如dom添加监听事件时,有两种,需要判断是否ie浏览器
    普通写法:

function addEvent(type, element, fun) {
    if (element.addEventListener) {
        element.addEventListener(type, fun, false);
    }
    else if(element.attachEvent){
        element.attachEvent(‘on‘ + type, fun);
    }
    else{
        element[‘on‘ + type] = fun;
    }
}

这种写法每一次都要执行if语句判断是否符合;

惰性函数法
函数重写(当有同名函数时,会被替换为后执行的)

function addEvent(type, element, fun) {
    if (element.addEventListener) {
        addEvent = function (type, element, fun) {
            element.addEventListener(type, fun, false);
        }
    }
    else if(element.attachEvent){
        addEvent = function (type, element, fun) {
            element.attachEvent(‘on‘ + type, fun);
        }
    }
    else{
        addEvent = function (type, element, fun) {
            element[‘on‘ + type] = fun;
        }
    }
    return addEvent(type, element, fun);
}
函数柯里化

柯里化函数是高阶函数的一种

  • 函数柯里化(function currying)又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包里被保存起来。待到函数真正需要求值的时候,之前传入的参数都会被一次性用于求值。
//普通函数
function add(x,y){
	return x+y;
}

//currying后
function curryingAdd(x){
	return function(y){
		return x+y;
	}
}
add(1,2);
curryingAdd(1)(2);
  • 应用:
    (1)参数复用
// 正常正则验证字符串 reg.test(txt)

// 函数封装后
function check(reg, txt) {
    return reg.test(txt)
}

check(/\d+/g, 'test')       //false
check(/[a-z]+/g, 'test')    //true

// Currying后
function curryingCheck(reg) {
    return function(txt) {
        return reg.test(txt)
    }
}

var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

hasNumber('test1')      // true
hasNumber('testtest')   // false
hasLetter('21212')      // false

上面的正则校验,如果有很多地方需要校验是否含有数字,将第一个参数reg进行复用,可以直接调用hasNumber,hasLetter函数。
(2)提前确认
(3)延迟执行

Function.prototype.bind=function(context){
	var _this=this;
	var args=Array.prototype.slice.call(arguments,1)
	return function(){
		return _this.apply(context,args)
	}
}

js中经常使用的bind,实现机制就是curring

  • 性能:
    (1)存取arguments对象通常要比存取命名参数慢一点
    (2)一些老版本的浏览器在arguments.length的实现上非常慢;
    (3)使用fn.apply()和fn.call()通常比直接调用fn()稍微慢点;
    (4)创建大量嵌套作用域和闭包会带来花销,无论在内存还是速度上。
    封装:
function curry(fn,args){
	var _this=that;
	var len=fn.length;
	var args=args||[];
	return function(){
		var _args=Array.prototype.slice.call(arguments);
		Array.prototype.push.apply(args,_args);
		//如果参数个数小于最初的fn.length,则递归调用,继续收集参数
		if(_args.length<len){
			return curry.call(_this,fn,_args);
		}
		return fn,apply(this,_args);
	}
}
  • 面试题:
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
    };

    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

add(1)(2)(3)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9
高阶函数

高阶函数是对其他函数进行操作的函数,它接收函数作为参数将函数作为返回值输出

闭包是典型的高阶函数

<script>
	function fn(callback){
		callback&&callback();//回调函数
}
fn(function(){alert('hi')})
</script>

<script>
function fn(){
	return function(){}
}
fn();
</script>

此时fn就是一种高阶函数
函数也是一种数据类型,同样也可以作为参数,传递给另外一个参数使用,最典型的就是作为回调函数。

正则表达式

正则表达式是用于匹配字符串中字符组合的模式

  • 测试正则表达式
    test() 存在 ture 不符合 false
    exec() 返回包含该查找字符串的数组

字符串的正则方法
match()
replace()
search()
split()

  • 特殊字符
    (1)边界符

^ 匹配开始
$ 匹配结束

(2)字符类
[abc] 包含a/b/c任一项即可

^ [abc]$ 三选一,只有是a,或者b,或者c
[^abc] 取反,不能包含a/b/c

(3)量词符

*0次或多次
+1次或多次(至少一个)
?0次或1次
{n}重复n次
{n,}重复n次或更多次
{n,m}重复n到m次

2、面向对象(OOP)和this处理

设计模式
  • 单例模式
    单例模式是最简单的模式之一,一个类只返回一个实例,一旦再次调用就直接返回,目的在于解决一个全局使用的类频繁地创建与销毁

  • 适配器模式

  • 代理模式

  • 策略模式

  • 发布-订阅者模式

  • 装饰器模式

————————————

类和实例(ES6+)
//创建类
class Star{
	//构造函数
	constructor(name){
		this.name=name;
		this.age=15;
	}
	//添加方法
	sing(){
		console.log(this.name+'唱歌');
	}
}

//实例化
var tmp=new Star('LiMing');
console.log(tmp.name)//LiMing
tmp.sing();//调用方法,对象.方法名()

//子类继承父类
class Son extends Star{
	constructor(){
		this.age=super(age);
	}
}

//调用子类
var tmpSon=new Son();
console.log(this.age);

!注意点:

  1. ES6中类没有变量提升,必须先定义类,再实例化对象
  2. 类里面共有的属性和方法必须用this;
  3. constructor里面的this指向实例化对象,方法里面的this指向这个方法的调用者;
  • 类的本质:就是个函数,可以简单理解为类就是构造函数的另外一种写法;以下构造函数的特性类都有,只是更好理解,更符合面向对象,因此类是一种语法糖(更便捷的写法)

  • ES6之前通过 构造函数+原型实现面向对象 编程
    (1)构造函数有原型对象prototype;
    (2)构造函数原型对象prototype 里面有 constructor 指向构造函数本身;
    (3)构造函数可以通过原型对象添加方法;
    (4)构造函数创建的实例对象有_proto_原型链指向 构造函数的原型对象;

原型和原型链
原型
  • 构造函数原型对象prototype
    只有构造函数有一个prototype属性,指向一个对象

作用:共享方法,可以把共享的不变的方法放在prototype对象里,这样对象的实例可以共享这些方法;

  • 对象原型_proto_ 只是指明一条线路,不能够赋值
    对象都会有一个属性 _proto_指向构造函数的prototype原型对象,之所以可以使用prototype原型对象的属性和方法,就是因为对象有_proto_原型的存在;

  • constructor 只有原型对象有

原型链

四万字 | web前端面试大纲整理_第1张图片

console.log(Star._proto_===Object.prototype);//true
  • 对象中都有一个属性_proto_,它指向上一级构造函数的prototype,直至Object._proto_为null,一层一层查找叫做原型链

  • 按照原型链方式的查找规则
    (1)当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性;有的话优先选中自身的值,就近原则;
    (2)如果没有就查找它的原型链(也就是_proto_指向的prototype原型队象);
    (3)如果还没有就查找原型对象的原型;
    (4)一次类推直到找到Object为止(null);
    (5)_proto_对象原型链的意义在于为对象原型查找机制提供一个方向,或者说一条路线。

new运算符的实现机制
  • new在执行时会做四件事情
    (1)在内存中创建一个新的对象;
    (2)让this指向这个对象
    (3)执行构造函数里面的代码,给这个新对象添加属性和方法;
    (4)返回这个新对象(所以构造函数里面不需要return)
call/apply/bind
1、call()
  • 可以调用函数
function fn(){
	console.log('aaa');
	console.log(this);
	//此时this指向window
}
fn.call();//打印出aaa
  • 修改函数this指向,第一个参数改变this指向,后面普通形参
    主要作用可以用来实现继承
function fn(x,y){
	console.log('aaa');
	console.log(this);
	//此时this指向对象o
	console.log(x+y)
}
var o={
	name:'LiMing'
}
fn.call(o,1,2);
2、apply()
  • 调用函数,改变this指向
    它的参数必须是数组形式的
fn.apply(thisArg,[argsArray])
//thisArg:在fun函数运行时指定的this值
//argsArray:传递的值,必须包含在数组里面
//返回的值就是函数的返回值,因为它就是调用函数
  • 主要应用,可以处理数组
    比如可以利用apply借助于数学内置对象求最大值
var arr=[1,55,44,212,8478];
Math.max.apply(Math,arr)//不需要改变this则重新指回Math
3、bind()
fn.bind(thisArg,arg1,arg2,……)
//thisArg:在fun函数运行时指定的this值
//arg1,arg2:传递其他参数;
//返回由指定的this值和初始化参数改造的原函数拷贝
  • bind()不会调用函数,但能改变函数内部的this指向
  • 返回的是原函数改变this指向之后的新函数
function fn(x,y){
	console.log('aaa');
	console.log(this);
	//此时this指向对象o
	console.log(x+y)
}
var o={
	name:'LiMing'
}
var f=fn.bind(o,1,2);//此时只是改变了this指向,没有调用函数
f();//调用函数

典型应用,有的函数不需要立即调用,又想改变函数内部this指向时应bind;

var btn=document.querySelector('button');
btn,onclick=function(){
	this.disabled=true;//按钮点击时候禁用
	setTimeout(function(){
		this.disabled=true;//直接调用时,this指的window,window中没有disabled属性
	}.bind(this),1000)//改变this指向,也可以用btn(不推荐),此时指向btn这个对象
	//如果用call或者apply会立即调用
}

bind()可以代替全局变量存储this的时候

  • call apply bind总结
    相同点:
    都可以改变函数内部内容;

不同点:
(1)call和apply会调用函数,并且改变函数内部this指向;
(2)call和apply传递的参数不一样,call传递参数aru1,aru2形式,apply必须数组形式;
(3)bind不会调用函数,可以改变函数内部this指向

  • 主要应用场景:
    (1)call经常做继承;
    (2)apply经常跟数组有关系,比如借助于数学对象实现数组的最大值和最小值;
    (3)bind不调用函数,但是还是想改变this指向,比如改变定时器内部this的指向
严格模式(ES5+,IE10)
  • 支持IE+,旧版本会忽略
    (1)消除了JavaScript语法不合理、不严谨之处,减少一些怪异行为;
    (2)消除代码运行的一些不安全之处,保证代码运行的安全;
    (3)提高编译器效率,增加运行速度;
    (4)禁用了在未来版本中可能会定义的一些语法,比如一些保留字符:class extends super等不能做变量名
  • 使用
    (1)全局使用,在整个script标签最上写 ‘use strict’,或写在立即执行函数里
    (2)局部使用,在函数内部最上方写 ‘use strict’。
  • 变化
    (1)在正常模式下,如果一个变量没有声明就赋值,默认是全局变量;严格模式禁止使用这种方法,变量丢必须先经过var命令声明,然后再使用;
    (2)严禁删除已经声明的变量,例如,delete x语法是错误的;
    (3)严格模式下this指向:
    + 全局作用域中this指向undefined;
    + 如果构造函数不加new调用,this会报错;
    + new 实例化的构造函数指向创建的对象实例;
    + 定时器里面的this还是指向window
    + 事件、对象还是指向调用者
    (4)函数变化
    + 不能有重名的形参;
    + 函数必须声明在顶层,不允许写在非函数代码块(比如if、for的大括号里)
constructor构造函数模式
  • 利用构造函数创建对象
function Star(uname,age){
	this.uname=uname;
	this.age=age;
	this.sing=function(){
		console.log(this.uname+'会唱歌')
	}
}

//实例化/创建对象
var tmp=new Star('LiMing',19);
tmp.sing();
console.log(tmp);

运行结果
四万字 | web前端面试大纲整理_第2张图片
构造函数中的属性和方法称为成员,成员可以添加
分为两种
(1)实例成员:构造函数内部通过this添加的成员,只能通过实例化的对象来访问

function Star(uname,age){
	this.uname=uname;
	this.age=age;
	this.sing=function(){
		console.log(this.uname+'会唱歌')
	}
}

//实例化/创建对象
var tmp=new Star('LiMing',19);
console.log(tmp.uname);//LiMing
console.log(Star.uname);//undefined

(2)静态成员:在构造函数本身添加的成员,静态成员只能通过构造函数来访问,不能使用对象访问。

function Star(uname,age){
	this.uname=uname;
	this.age=age;
	this.sing=function(){
		console.log(this.uname+'会唱歌')
	}
}
Star.sex="男";
//实例化/创建对象
var tmp=new Star('LiMing',19);
console.log(tmp.sex);//undefined
console.log(Star.sex);//男
  • 构造函数直接放在里面存在内存浪费问题(每实例化一个对象都要重新开辟空间),因此把共享的方法放入prototype中,不需要开辟新的内存空间;每一个构造函数都有一个原型对象prototype属性

此时tmp和tmp2对象会分别开辟空间存放sing方法,同样的函数开辟了不同的空间,内存浪费

function Star(uname,age){
	this.uname=uname;
	this.age=age;
	this.sing=function(){
		console.log(this.uname+'会唱歌')
	}
}
//实例化/创建对象
var tmp=new Star('LiMing',19);
var tmp2=new Star('John',23);
console.log(tmp.sing===tmp2.sing)//false,地址不同
function Star(uname,age){
	//构造函数内部
	this.uname=uname;
	this.age=age;
}
//使用原型对象prototype,将共享的方法放入
Star.prototype.sing=function(){
	console.log('我会唱歌')
}
//实例化/创建对象
var tmp=new Star('LiMing',19);
var tmp2=new Star('John',23);
console.log(tmp.sing===tmp2.sing)//true,地址相同

实例对象能调用prototype里的方法,是因为每一个实例对象中有_proto_属性(对象原型),指向构造函数的原型对象prototype

=>方法查找规则:
(1)首先先看tmp对象上是否有sing方法,如果有就执行这个对象上的sing();
(2)如果没有sing这个方法,因为有_proto_的存在,就去构造函数原型对象prototype上去查找sing();

打印实例化对象:
四万字 | web前端面试大纲整理_第3张图片
打印构造函数:
四万字 | web前端面试大纲整理_第4张图片

一般情况下,公共属性定义到构造函数里面(比如上面的uname,age),公共的方法放到原型对象上

constructor 构造函数
对象原型(proto)和原型对象(prototype)里面都有一个constructor属性,成为构造函数,因为它指回构造函数本身
四万字 | web前端面试大纲整理_第5张图片
四万字 | web前端面试大纲整理_第6张图片
很多情况下,我们需要手动的利用constructor这个属性指回原来的构造函数
当方法比较多时,我们可能用对象方式来定义方法

//赋值,此时Star里面的constructor被覆盖了
Star.prototype = {
	sing:function(){
		console.log('我会唱歌');
	}
	dance:function(){
		console.log('我会跳舞');
	}
}

四万字 | web前端面试大纲整理_第7张图片

手动返回constructor构造函数

Star.prototype = {
	constructor:Star,//手动返回constructor
	sing:function(){
		console.log('我会唱歌');
	},
	dance:function(){
		console.log('我会跳舞');
	}
}

四万字 | web前端面试大纲整理_第8张图片
构造函数、实例、原型对象三者关系
四万字 | web前端面试大纲整理_第9张图片

  • 应用
    (1)扩展内置对象
    可以通过原型对象,对原来的内置对象进行扩展自定义方法,注意通过.方法()添加,不要直接等于对象的方式,会覆盖掉之前的;
    (2)借用构造函数继承父类型属性
JS中this八种情况综合梳理

一般指向执行时的对象而不是定义时,箭头函数相反
四万字 | web前端面试大纲整理_第10张图片

  • (1)在一般函数方法中使用this指代全局对象=>函数名()调用
function test(){
	this.x=1;//这里的this就是window
	console.log(this.x);//1,相当于声明了个全局变量
}
test();//等价于window.test()
  • (2)作为对象方法调用,this指代上级对象=>对象.函数名()调用
    多级嵌套,对象.对象.函数名(),this指向调用它的那个对象,即被调用函数的上一级对象(要看最后执行时)
var x=3;
function test(){
	console.log(this.x)//1,此时this指的是对象O
	console.log(x);//3 此时是全局变量x,相当于window.x
}
var o={
	x:1,
	m:test
};
o.m();
  • (3)作为构造函数使用,this指代new出的对象=>new关键字调用
function test(){
	this.x=3;
	console.log(this);//{text:3}
}
var o=new test();
console.log(o.x);//3

(1)在构造函数中,里面的new指向的是实例对象;
(2) 原型对象函数里面的this,指向的是实例对象;
!注意如果返回值是一个对象(除null外),那么this指向返回的那个对象,如果返回值不是对象那么this还是指向函数实例

  • (4)apply()/call()/bind()(ES5新增)绑定时,this指向绑定对象
    call方法第一个参数是this指向,后面传入的是第一个参数列表。当地一个参数为null、undefined时,默认指向window。
    apply方法接受两个参数,第一个参数是this的指向,第二个参数是一个参数数组。当第一个参数为null、undefined的时候,默认指向window。
    bind方法和call方法很类似,第一个参数是this的指向,从第二个参数开始是接收的参数列表。区别在于bind方法返回值是函数以及bind接收的参数列表的使用。
var x=0;
function hello(){
	console.log(this.x)
}
var h={};
h.x=1;
h.m=hello;
h.m.apply();//0
h.m.apply(h);//1

面试题:

var name = 'global';
var obj = {
    name : 'obj',
    dose : function(){
        this.name = 'dose';//obj调用了dose方法,将obj的name改为dose
        return function(){//内部匿名函数,this指向window
            return this.name;
        }
    }
}
console.log(obj.dose().call(this))//global
console.log(obj.name);//dose
  • (5)作为数组的一个元素,通过数组下标调用时,this指向这个数组

  • (6)函数作为window内置函数的回调函数调用时:this指向window.setTimeout(),setInterval()等,如果是匿名函数,指向window

  • (7)严格模式下(设置了“use strict”),this指向undefined

function hello() { 
   'use strict';
   console.log(this);  // undefined
}  
hello();
  • (8)箭头函数中的this问题,指向为定义时的this,而不是执行时的this
    默认指向定义它时,所处上下文的对象的this指向,即ES6箭头函数里this的指向就是上下文里对象this指向,偶尔没有上下文时,this就指向window;
    即使是call()/apply()/bind()等方法也不能改变箭头函数this的指向
  • (9)事件绑定时指向事件调用者
JS中的四大数据类型检测方案
浅拷贝和深拷贝
浅拷贝

浅拷贝只是拷贝一层,更深层次的对象级别的只拷贝引用
当修改其中任意一个数据都会影响另一个

var obj={
	id:,
	name:'aaa',
	msg:{
		age:18
	}
}
//1、for in 拷贝
var o={},
for(var k in obj){
	o[k]=obj[k];//msg中的嗯对象只是拷贝的一个引用地址
}
//2、es6语法槽拷贝Object.assign(target,...sources)
Object.assign(o,obj)
//3、es6中结构赋值
var o={...obj}

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

深拷贝

深拷贝拷贝多层,每一级别的数据都会拷贝
内存空间重新复制了一份

//1、JSON.stringify
//当对象中包含正则、函数、undefined时会失败
JSON.parse(JSON.stringify(obj))
//2、函数递归
function deepCopy(newobj,oldobj){ 
	for(var k in oldobj){
		//获取属性值 oldobj[k]
		var item=oldobj[k];
		//判断是否是数组
		if(oldobj[k] instanceOf Array){
			newobj[k]=[];
			deepCopy(newobj[k],item);
		}
		//判断是否是对象
		else if(oldobj[k] instanceOf Object){
			newobj[k]={};
			deepCopy(newobj[k],item);
		}else{
			//简单数据类型
			newobj[k]=item;
		}
	}
}
deepCopy(o,obj)
JS中的四大继承方案
组合继承

es6之前不提供extends继承,可以通过构造函数+原型对象模拟实现继承,称为组合继承;

借用构造函数继承父类型属性
核心原理:通过call()把父类型的this指向子类的this,可以实现子类型继承父类型属性

//父构造函数
function Father(uname,age){
	//this指向父构造函数的对象实例
	this.uname=uname;
	this.age=age;
}
//子构造函数
function Son(uname,age){
	//this 指向子构造函数的对象实例
	//通过call改变this的指向
	Father.call(this,uname,age);
}
//实例化一个子构造函数对象
var son=new Son('LiMing',16);
console.log(son);

子构造函数里面继承了父构造函数的两个实例
四万字 | web前端面试大纲整理_第11张图片
继承父构造函数里面的方法

//父构造函数
function Father(uname,age){
	//this指向父构造函数的对象实例
	this.uname=uname;
	this.age=age;
}
//父亲实例对象里面的方法
Father.prototype.money=function(){
	console.log('父亲的钱')
}
//子构造函数
function Son(uname,age){
	//this 指向子构造函数的对象实例
	//通过call改变this的指向
	Father.call(this,uname,age);
}
//子构造函数继承父构造函数
Son.prototype=Father.prototype;
//实例化一个子构造函数对象
var son=new Son('LiMing',16);
console.log(son);

此时子构造函数里继承了父构造函数的方法
四万字 | web前端面试大纲整理_第12张图片
但是,存在一个问题,此时子构造函数和父构造函数实际指向同一个地址,如果改变子构造函数,同时也会改变父亲(浅拷贝)

//给子构造函数添加一个方法
Son.prototype.score=function(){
	console.log('孩子成绩是90');
}
console.log(son);
console.log(Father.prototype);

父构造函数里面也有score方法
四万字 | web前端面试大纲整理_第13张图片
因此不能直接赋值,通过构造函数给子构造函数赋值

//实际是创建了一个Father的实例对象,使Son的原型对象指向Father的实例对象
Son.prototype=new Father();
Son.prototype.score=function(){
	console.log('孩子成绩是90');
}
console.log(son);
console.log(Father.prototype);

子继承了父的money方法,但是父里面没有子的score方法
四万字 | web前端面试大纲整理_第14张图片
如果利用对象的的方式改变了原型对象,要利用constructor指向回来

Son.prototype=new Father(){
	constructor:Son,
};
递归函数

递归函数:函数内部可以调用其本身,这个函数就是递归函数
递归很容易出现“栈溢出”(死循环),因此必须加退出条件,return;

应用
(1)利用递归函数求1~n的阶乘1 * 2 * 3 * 4*……n

function fn(n){
	if(n==1){
		return 1;
	}
	return n*fn(n-1)
}
fn();

(2)斐波那契数列

function feibonacci(n){
	if(n<=0){
	return 0;
	}
	if(n===1||n===2){
		return 1;
	}
	return f(n-1)+f(n-2);
}

(3)根据id返回对应的数据对象

var data=[{
	id:1,
	name:'aaa',
	goods:[{
		id:l1,
		gname:'hhh'
	},
	{
		id:l2,
		gname:'lll'
	}
	]
	},
	{
	id:2,
	name:'bbb',
	}
]
function getId(json,id){
	json.forEach(function(item){
		if(item.id==id){
			console.log(item)
			o=item;
		}else if(item.goods&&item.goods.length>0){
			getId(item.goods,id)
		}
	})
	return o;
}

3、DOM/BOM及事件处理机制

DOM/BOM的核心操作
事件对象
拖拽及拖拽插件封装
发布订阅设计模式
深度剖析JQ源码
事件传播机制和事件代理
DOM2级事件的核心运行机制;
移动端Touch/Gesture事件及封装处理
浏览器底层渲染机制和DOM的回流重绘
DIALOG模态框组件的封装
MVC、MVVM

4、ES5/ES6/ES7的核心知识

ES5
数组方法
  • 迭代(遍历方法):
    (1)forEach() IE9+(类似jq里面的each ,增强版for循环)
    forEach()里return不会终止迭代
array.forEach(function(vaue,index,arr){

})
//value:数组当前项的值
//index:数组当前项的索引值
//arr:数组对象本身

(2) map()
(3)filter() 筛选数组 return回来
filter()方法创建一个新数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组
它直接返回一个新的数组

array.filter(function(value,index,arr){
})
//value:数组当前项的值
//index:数组当前项的索引值
//arr:数组对象本身

(4)some() 查找数组中是否有满足条件的元素,有一个就不再执行循环
some()方法用于检测数组中的元素是否满足指定条件
返回值是布尔值,有则返回true,没有则返回false

array.some(function(value,index,arr){
})
//value:数组当前项的值
//index:数组当前项的索引值
//arr:数组对象本身

(5)every()

字符串方法
  • trim() 从一个字符串的两端删除空白字符,中间的不去掉
    不影响字符串的本身,返回一个新的字符串
str.trim()
对象方法

Object.defineProperty() 定义对象中新属性或修改原有的属性

Object.defineProperty(obj,prop,descriptor)
//obj:必需,目标对象
//prop:必需,需定义或修改属性的名字
//descriptor:必需,目标属性所拥有的特性

descriptor以对象{}书写,有以下几种属性
(1)value:设置属性的值,默认为undefined;
(2)writable:值是否可以重写,true|false 默认为false;
(3)enumerable:目标属性是否可以被枚举(是否能遍历),true|false 默认为false;
(4)configurable:目标属性是否可以被删除或是否可以再次修改特性 true|false 默认为false
例:

var obj={
	name:'LiMing',
	age:16
}
Object.defineProperty(obj,'sex',{
	value:'男';//添加了sex	
})
Object.defineProperty(obj,'age',{
	value:18;//修改了了sex
})
console.log(obj);

四万字 | web前端面试大纲整理_第15张图片

Object.defineProperty(obj,'name',{
	writable:false;//不允许修改,true 可修改
})
ES6
let/const及和var的区别
  • let
    ES6中新增的用于变量声明的关键字
    (1)let声明的变量只在所处于的块级有效(大括号内有用)
    (2)不存在变量提升,必须先声明再使用

  • const
    声明常量,常量就是值(内存地址)不会变化的值(js不需要实时监控,效率比较高)
    (1)具有块级作用域
    (2)声明常量时必须赋值

四万字 | web前端面试大纲整理_第16张图片

箭头函数ArrowFunction

用来简化函数定义语法的
()=>{}
(1)函数体只有一句代码,且代码的执行结果就是返回值,可以省略大括号

const sum=(n1,n2)=>{
	return n1+n2;
}
//可以简化为
const sum=(n1,n2)=>n1+n2;

(2)如果形参只有一个,形参外侧的小括号可以省略

const fn=n1=>n1*2;

(3)箭头函数不绑定this关键字,箭头函数中的his,指向函数定义位置的上下文this

var age=100;
var obj={
	age:20,
	say:()=>{
		console.log(this.age);
	}
}
obj.say();//100 对象不能产生作用域,实际是window
解构赋值和拓展运算符
  • 解构赋值
    ES6允许从数组中提取值,按照对应位置,对变量赋值
    (1)数组解构
    数组结构允许我们按照一一对应的关系从数组中提取值,然后将值赋值给变量
let arr=[1,2.3];
let ary2[a,b,c]=arr;
console.log(ary2)//[1,2,3]

(2)对象解构
使用变量的名字匹配对象的属性
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined
  • 剩余参数
    (1) 可以接收多个参数
const sum=(...args)=>{
	let total=0;
	args.forEach(item=>total+=item);
	return total;
}
console.log(sum(10,20,30));//60
console.log(sum(10,20));//30

(2)剩余参数和结构配合使用

let ary1=['aaa','bbb','ccc'];
let [s1,...s2]=ary1;
console.log(s1);//aaa
console.log(s2);//['bbb','ccc']
  • 扩展运算符 展开语法
    (1)扩展运算符可以将数组或对象转为用逗号分隔的参数序列
let ary=[1,2,3]
console.log(...ary)//1 2 3,逗号被当做参数分隔符
//相当于
console.log(1,2,3)

(2)应用于合并数组

//方法一
let ary1=[1,2,3];
let ary2=[4,5,6];
let ary3=[...ary1,...ary2];
console.log(ary3);[1,2,3,4,5,6];
//方法二
ary1.push(...ary2)

(3)将类数组或可遍历对象转换为真正的数组

var odivs=document.getElementByTagName('div');
var arr=[...odivs]
ES6 的内置对象扩展

Array的扩展方法

  • 构造函数方法:Array.from()
    (1)将类数组或可遍历对象转换为真正的数组
let arrayLikes={
	'0':'a',
	'1':'b',
	'2':'c',
	length:3
}
let arr2=Array.from(arrayLikes);
console.log(arr2);['a','b','c']

(2)还可以接收第二个参数,作用类似于数组中的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组;

let arrayLikes={
	'0':1,
	'1':2,
	'2':3,
	length:3
}
let arr2=Array.from(arrayLikes,item=>item*2);
console.log(arr2);//[2,4,6]
  • find()
    用于找出第一个符合条件的数组成员,如果没有找到返回undefined
let ary=[{
	id:1,
	name:'aaa'
},{
	id:2,
	name:'bbb'
}];
let target=ary.find((item,index)=>item.id==2);
console.log(target);//{id: 2, name: "bbb"}
  • findIndex()
    用于找出符合条件的数组成员的位置,如果没有找到返回-1
let ary=[1,2,3,8,9];
let index=ary.findIndex((value,index)=>value>3);
console.log(index);//3
  • includes()
    表示某个数组是否包含给定的值

String 的扩展方法

  • 实例方法: startsWith()endsWith()
    startsWith():表示参数字符串是否在元字符串的头部,返回布尔值;
    endsWith():表示参数字符串是否在元字符串的尾部,返回布尔值;
let str="aaabbb!";
str.startsWith('a')//true
str.endsWith('!')//true
  • repeat()
    表示将原字符串重复n次,返回一个新的字符串
'abc'.repeat(3);//abcabcabc
Set/Map数据结构

Set 数据结构
类似于数组,但是成员的值都是唯一的,没有重复的值

  • Set本身是一个构造函数,用来生成Set数据结构
const s=new Set();
console.log(s)
  • Set函数可以接收 一个数组作为参数,用来初始化
const arr=new Set([1,2,3,4,5])
  • 应用:数组去重
let arr=[1,6,4,5,1,2]
let arr2=new Set(arr);
console.log(arr2);//{1, 6, 4, 5, 2}
  • 实例方法:
    (1)add(value):添加某个值,返回Set结构本身
    (2)delete(value):删除某个值,返回一个布尔值,表示删除是否成功
    (3)has(value):返回一个布尔值,表示是否为Set的成员
    (4)clear():清除所有成员,没有返回值

  • 遍历
    forEach()方法,对每个成员执行遍历操作,没有返回值

Promise设计模式

Promise对象:代表了未来某个将要发生的事件(通常是异步)
可以解决回调地狱的问题,减少回调,把配置和结果回调分开

Promise只能通过resolve或者reject函数来返回,并使用then()或者catch()获取,不能在new Promise里面直接return,这样是获取不到Promise返回值的

//创建成功的promise
var p1=Promise.resolve('这里是成功的promise对象');
var p2=new Promise(function(resolve,reject){
	resolve('这里是成功的promise对象');
})
console.log(p1);
p1.then(function(res){
	console.log(res)
})

//创建失败的promise
var p1=Promise.reject('这里是失败的promise对象');
var p2=new Promise(function(resolve,reject){
	reject('这里是失败的promise对象');
})
console.log(p1);
p1.catch(function(error){
	console.log(error)
})
  • Promise.all
    可以包装多个promise实例
    应用:进度条
    resolve返回都完成
    reject 有一个失败,失败的原因是第一个promise失败的结果
Promise.all([promise1,promise2,promise3]).then((values)=>{
	console.log(values)
})
  • Promise.race 竞速 谁先有结果,就调用谁
    返回一个promise,一旦迭代器摩尔个promise解决或拒绝,返回的promise就会解决或拒绝
    应用:红包雨
Promise.race([p1,p2,p3]).then((res)=>{
})
async/await及实现原理

async
用来声明一个异步的function

await
用于等待一个异步任务完成的结果

Insterator迭代器

迭代器是种特殊的对象,每一个迭代器都有一个next(),该方法返回一个对象,包括value和done属性

Geneator生成器函数

生成器是函数,用来返回迭代器

Promise A+规范(手写promise源码)
JS底层运行机制:单线程和同步异步编程
JS底层运行机制:微任务和宏任务和事件循环机制
Insterator迭代器和for of循环

5、AJAX/http前后端数据交互

AJAX核心四部操作
GET/POST核心机制与区别
TCP三次握手和四次握手
axios库和源码剖析
fetch基础和实战应用
前端开发中的9种跨域方案
HTTP网络状态码和实战中的处理方案
前端性能优化汇总(包括强缓存和弱缓存)

6、企业级OA

登录注册的前后端处理机制
加密策略:encodeURI-Componret 和MD5
存储方案:cookie/webStorage/session
用户权限和登录状态的校验处理
Token的校验处理
JQ/Bootstrap

安全

XSS

关于

xss全称叫 Cross Site Script ,顾名思义就是跨站脚本攻击,XSS攻击是通过在网站注入恶意的脚本,然后借助脚本改变原有的网页,当用户访问网页时,对浏览器一种控制和劫持,XSS攻击主要分为以下几种类型。

  • 反射型XSS:攻击者通过特定的方式来诱惑受害者去访问一个包含恶意代码的URL;
  • 存储型XSS:将恶意脚本放到服务器中,下次只要受害者浏览包含此恶意脚本的页面就会自动执行恶意代码;
  • 基于DOM型XSS:客户端的js对页面dom节点进行动态的操作,比如插入、修改页面的内容;
  • SQL注入:通过客户端输入把SQL命令注入到一个应用的数据库中,从而执行恶意的SQL语句。
防范措施
  • Cookie设置HttpOnly /
    SecureOnly(只允许https请求读取)和HostOnly(只允许主机域名与domain一致)

  • 检查输入内容(对用户输入的所有内容进行过滤和检查)

    比如是否包含一些特殊字符、<、>等,将它们准话为实体字符;

    将不可信数据作为URL参数值时需要对URL编码(encodeURIComponent)

CSRF

关于CSRF

CSRF全称Cross Site Request Forgery ,跨站请求伪造,通过劫持受信的用户(已登录)向服务器发送非用户本身意愿的请求,以此来完成攻击,生活中用户很多验证信息都是存在于cookie中,不法分子可以利用用户自己的cookie来通过安全验证。

具备以下两个特点

  • 一般发生在第三方域名
  • CSRF攻击者不能获取到Cookie等信息,只能用来通过安全验证
防范措施
  • 验证码(用户提交操作之前,添加验证码验证环境)
  • Referer源的检测(同源检测),拦截器
  • 请求地址添加token验证(CSRF Token)
  • Samesite Cookie 通过设置SamesiteCookie为Strict,浏览器在任何跨域都不会带上cookie

Hybrid/APP/小程序

  • Hybird
  • uni-app
  • RN
  • Flutter
  • 小程序
  • Weex
  • PWA

工程化方面

  • webpack
  • git
  • linux/nginx

全栈方面

  • node
  • express
  • koaz
  • mongodb
  • nuxt.js/next.js

框架方面

vue

vue-cli 3.0(2.0向3.0过渡)
  • 配置
  • 优化
  • 区别
vue基础知识
MVC、MVP、MVVM

mvc:视图(view)—>路由/控制器(controller)—>数据(model) 单向绑定
mvp:model<=>presenter(中间人)<=>view
mvvm:view<=>viewModel<=>model 双向绑定

这三者都是框架模式,它们的设计目标都是为了解决Model和View的耦合问题
MVC模式出现较早应用在后端,如SpringMVC、ASP.NET MVC等,在前端领域的早期也有应用。有点事分层清晰,缺点是数据流混乱、灵活性带来的维护性问题
MVP
MVVM不仅解决MV耦合问题,还同时解决了维护两者映射关系的大量繁杂代码和DOM操作代码,在提高效率

双向数据绑定
响应式数据原理

1、核心点:Object.defineProperty
2、默认vue在初始化数据时,会给data中的属性使用Object.defineProperty重新定义所有属性,获取和设置时添加监听(拦截)
vue
3、原理
四万字 | web前端面试大纲整理_第17张图片

vue中是如何检测数据变化

1、使用函数劫持的方法,重写了数组的方法通过原型链把数组的方法进行重写(拦截七个:push、pop、shift、unshift、splice、sort、reverse)只要调用了这几个方法,就通知视图更新,对插入的数据再次观测
2、vue将data中的数组,进行了原型链的重写,指向了自己定义的数组原型方法,这样当调用数组api时,可以通知依赖更新,如果数组中包含着引用类型,会对数组中的引用类型再次进行监控。数组里的对象也会导致数组更新
四万字 | web前端面试大纲整理_第18张图片

为什么vue采用异步渲染

1、vue是组件级更新。如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,所以为了性能考虑,vue会在本轮数据更新后,再去异步更新视图。
四万字 | web前端面试大纲整理_第19张图片

nextTick实现原理

nextTick()方法主要是使用了宏任务微任务定义了一个异步方法,多次调用nextTick方法会将方法存入队列中,通过这个异步方法清空当前队列,所以这个nextTick方法就是异步方法
四万字 | web前端面试大纲整理_第20张图片

vue中computed的特点

1、computed watch method的区别
computed 和 method
method (方法):只要每次一变化就会重新的数据渲染 性能开销大
computed(计算属性):具备缓存,渲染的时候如果依赖的属性没有发生变化,就不会导致方法重新执行
computed 和 watch
内部都使用watcher

先拿到用户定义的事件(函数)
lazy
四万字 | web前端面试大纲整理_第21张图片
watch中的deep:true

单页应用
data为什么是函数

函数每次执行都会返回全新的data实例

vue组件可能存在多个实例,如果使用对象形式定义data,则会导致他们共用一个data对象,那么状态将会变更将会影响所有组件实例,这是不合理的;采用函数定义,在initData时将其作为工厂函数返回全新data对象,有效规避多实例之间状态污染。而在Vue根实例创建过程中则不存在限制,因为根实例只有一个。

对象是对于内存地址的引用。直接定义个对象的话,组件之间都会使用这个对象,这样会造成组件之间数据相互影响。组件就是为了抽离开来提高复用的, 如果组件之间数据默认存在关系,就违背了组件的意义。 函数 return 一个新的对象,其实还是为 data 定义了一个对象, 只不过这个对象是内存地址的单独引用了,这样组件之间就不会存在数据干扰的问题。

v-model的原理
v-if和v-for哪个优先级更高

结论:v-for高于v-if
如果同时出现,每次渲染都会先执行循环再判断,无论如何都不可避免
改进:1、把v-if放在外面一层
2、把判断放在数据渲染之前

v-if和v-show的区别
vue生命周期
父子组件生命周期顺序
vue的单向数据流
keep-alive组件
slot插槽
vue检测数组或对象的变化
虚拟dom
vue中key的作用

源码:src\core/vdom/patch.js-updateChildred()
1、key的作用主要是为了高效的更新虚拟dom。其原理是vue在patch过程中通过key可以精准的判断两个节点是否是同一个,从而避免频繁更新不同元素,是的整个patch过程更高效,减少dom操作量,提高性能。
2、另外,若不设置key还可能在列表更新时引发一些隐蔽的bug;(比如说应该更新的但是没有更新)
3、vue中在使用相同标签名元素的过渡切换时,也会使用到key元素,其目的也是为了让vue可以区分它们,否则vue只会在其内部属性而不会触发过渡效果。

vue中的diff算法

非vue专用,虚拟dom都会使用
1、必要性 lifecycle.js-mountComponent()
组件中可能存在很多个data中的key的使用
2、执行方式,patch.js-patchVnode()
patchVnode是diff发生的地方,整体策略:深度优先,同层比较
3、高效性,patch.js-undateChildren()

  • diff算法是虚拟dom技术的必然产物:通过新旧虚拟dom作对比(即diff),将变化的地方更新在真实DOM上;另外,也需要diff高效的执行对比过程,从而降低时间复杂度O(n)
  • vue2.x中为了降低Watcher粒度,每个组件只有一个Watcher与之对应,只有引入diff才能精确找到发生变化的地方
  • vue中diff执行的时刻是组件实例执行其更新函数时,它会比对上一次渲染结果oldVnode和新的渲染结果newVnode,此过程称为patch。
  • diff过程整体遵循深度优先、同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或者文本节点做不同操作;比较两组子节点是算法的重点,首先假设头尾节点可能相同做4次比对尝试,如果没有找到相同节点,因此整个patch过程非常高效
vuex
vue-router的两种模式
vue-router有哪几种导航钩子
vue组件化
  • 定义:独立的模块切分出来,有更好的复用性
    src\core\global-api\assets.js
//全局定义
Vue.component('comp',{
	template:'
this is a component
' }) //组件定义 <template> <div> this is a component </div> </template>

vue-loader会编译template为render函数,最终导出的依然是徐建配置对象(js对象)

  • 优点:维护性、测试性、维护性
    lifecycle.js-mountComponent()
    组件、watcher,渲染函数、更新函数之间的关系
    每一个组件都有一个watcher,组件中的数据发生变化,只会调用这个组件的渲染函数、更新函数
    合理切割组件粒度,经常渲染的组件独立出来,效率更好

-组件化实现:
构造函数:src\core\global-api\extend.js
实例化及挂载:src\core\vdom\patch.js-createElm()

1、组件是独立和可复用的代码组织单元。组件系统是Vue核心特性之一,它使开发者使用小型、独立和通常可复用的组件构建大型应用;
2、组件化开发能大幅提高开发效率、测试性、复用性
3、组件按分类有:页面组件、业务组件、通用组件
4、vue的组件是基于配置的,我们通常编写的组件是组件配置而非组件,框架后续会生成其构造函数,它们基于VueComponent,扩展于Vue;
5、vue中常见的组件化技术有:属性prop,自定义事件、插槽等,它们主要用于组件通信、扩展等;
6、合理的划分组件,有助于提高应用性能;
7、组件应该是高内聚、低耦合
8、遵循单向数据流原则

vue性能优化方法
  • 1、路由懒加载
const router=new VueRouter({
	routes:[
		{path:'/foo',component:()=>import('./Foo.vue')}
	]
})
  • 2、keep-alive缓存页面

  • 3、使用v-show复用DOM

  • 4、v-for遍历避免同时使用v-if

  • 5、长列表性能优化
    如果列表时纯粹的数据展示,不会有任何变化,就不需要做响应化
    如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域内容

  • 6、事件销毁
    Vue组件销毁时,会自动解绑它的全部指定

  • 7、图片懒加载 v-lazy
    图片需要显示时才去加载

  • 8、第三方插件按需引入、无状态的组件标记为函数式组件、子组件分割

  • 双向数据绑定

  • template模板渲染语法和原理(vue-loader,虚拟DOM)

  • 指令和自定义指令

  • methods computed watch filters

  • class/style

  • 条件渲染和循环渲染

  • 事件处理

  • 表单处理

  • 组件(属性)

  • ref

  • 生命周期

  • 插槽slot

  • transition

  • 渲染函数和jsx

  • 插件编写

  • 混入

  • devtools

vue-router
  • 基础知识
  • 动态路由
  • 编程式导航
  • 命名路由和命名容器
  • 导航守卫
  • HASH/BRWSER路由
  • 路由原理

##、## 单元测试

SSR服务器渲染nuxt.js
UI组件库

react

creat-react-app
  • 配置
  • 优化
react基础
  • JSX语法(虚拟DOM)
  • 状态
  • 属性
  • ref
  • 组件
  • 生命周期
  • PureComponent
  • Hooks
react-router-dom
redux
  • redux
  • react-redux
  • 中间件
dva
umi
typescript
UI组件
SSR服务器渲染next.js

算法

  • 数组去重
  • 排序算法
  • 递归调用

手写代码

防抖和节流
  • 防抖:
    将多次触发变成最后一次触发,在n秒内只能执行一次
function debounce(fn,wait){
	let timer=null;
	return function(){
		let context=this;
		let args=arguments;
		clearTimeout(timer);
		timer=setTimeout(function(){
			fn.call(context,args)
		},wait)
	}
}
function fn(context,args){
	console.log("防抖");
}
addEventListener('scoll',debounce(fn,1000))
  • 节流:
    将多次执行变成每隔一个时间点执行一次,特定的时间只执行一次
//时间戳版
function throttle(fn,wait){
	let prev=Date.now();
	return function(){
		let context=this;
		let args=arguments;
		let now=Date.now();
		if(now-prev>=wait){
			fn.apply(context,args);
			prev=Date.now();
		}
	}
}
function fn(context,args){
	console.log("节流");
}
addEventListener('scroll',throttle(fn,1000));

//定时器版
function throttle(fn,wait){
	let timeout;
	return function(){
		let context=this;
		let args=arguments;
		if(!timeout){
			timeout=setTimeout(function(){
				timeout=null;
				fn.apply(context,args)
			},wait)
		}
	}
}
浅拷贝和深拷贝
  • 浅拷贝
let oldobj={
	name:'aaa',
	age:18,
	goods:{
		color:'red'
	}
}
let newobj={};
//1、for in 循环
for(let k in oldobj){
	newobj[k]=oldobj[k];
}
//2、es6解构赋值
newobj={...oldobj}
//3、es6assign
Object.assign(newobj,oldobj)
  • 深拷贝
//1、JSON.stringify
//当对象中包含函数、正则、undefined时会失败
newobj=JSON.parse(JSON.stringify(oldobj));

//2、递归拷贝
function deepCopy(newobj,oldobj){
	for(var k in oldobj){
		let item=oldobj[k];
		if(item instanceof Array){
			newobj[k]=[];
			deepCopy(newobj[k],item)
		}else if(item instanceof Object){
			newobj[k]={};
			deepCopy(newobj[k],item)
		}else{
			newobj[k]=item
		}
	}
	return newobj;
}
deepCopy(newobj,oldobj)
数组扁平化
let arr=[
	[1,2,3],
	[4,5,6],
	[7,8,[9,10,[11,12],13]]
]

1、es6中flat()方法

arr=arr.flat(Infinity);
//括号中代表扁平化几级,默认1级,Infinity无限级

2、toString()方法

arr=arr.toString().split(',').map(item=>parseFloat(item))
//.map(item=>parseFloat(item))转换为数字

3、JSON.stringify()方法

arr=JSON.stringify(arr).replace(/(\[|\])/g,'').split(',').map((item)=>parseFloat(item))

4、join()方法

arr=arr.join(',').split(',').map((item)=>parseFloat(item))

5、some()方法判断,扩展运算符

while(arr.some(item=>Array.isArray(item))){
	arr=[].concat(...arr)
}

6、递归。递归遍历每一项,若为数组则重新递归

function flat(arr){
	var res=[];
	res.map(item=>{
		if(Array.isArray(item)){
			res=res.concat(flat(item))
		}else{
			res.push(item);
		}
	})
	return res;
}
单例模式

一个类有且只有一个实例(确保多次构造函数时,都返回一个实例)
即满足以下条件

function A(){

}
var a1=new A();
var b1=new A();
a1===b1//true

核心思路:每次调用构造函数,返回指向同一个对象的指针。即,只在第一次调用时创建新对象,之后调用返回该对象,即,缓存初次创建的变量对象海报
1、使用构造函数的静态属性

function A(name){
	if(typeof(A.instance)==='Object'){
		return A.instance;
	}
	this.name=name;
	//缓存
	A.instance=this;
	return this;	
}

2、利用闭包(当对象第一次被创建后,重写构造函数,在重写后的构造函数里访问私有变量)

function A(name){
	var instance=this;
	this.name=name;
	//重写构造函数
	A=function(){
		return instance;
	}
	//方法一:原型链继承
	A.prototype=this;
	//方法二:直接指向旧的原型
	A.prototype=this.constructor.prototype;
	
	instance=new A();
	//调整构造函数指针
	instance.constructor=A;
	return instance;
	
}
数组去重
let arr=[1,,null,1,2,'null',undefined,undefined,{},{},NaN,NaN,3,5,null,true,false,true,false]

1、利用es6中Set()方法(es6常用)

let array=new Set(arr);
let array=Array.from(new Set(arr));
let array=[...new Set(arr)]
//无法去掉空对象

2、for循环用splice

function unique(arr){
	for(var i=0;i<arr.length;i++){
		for(var j=i+1;j<arr.length;j++){
			if(arr[i]==arr[j]){
				arr.splice(j,1)
				j--;//使用splice删除原数组后,数组会塌陷,给j--或创建一个新数组存放
			}
		}
	}
	return arr;
}
//NaN,{}没有去重,null和true直接消失

3、indexOf

function unique(arr){
	var array=[];
	for(var i=0;i<arr.length;i++){
		if(array.indexOf(arr[i])==-1){
			array.push(arr[i])
		}
	}
	return array;
}
//NaN,{}无法去掉

4、sort()

function unique(arr){
	arr=arr.sort();
	var array=[arr[0]];
	for(var i=0;i<arr.length;i++){
		if(arr[i]!==arr[i-1]){
			array.push(arr[i]);
		}
	}
	return array;
}
//null,NaN,{}没有去重

5、利用对象属性不能相同

function unique(arr){
	var array=[];
	var obj={};
	for(var i=0;i<arr.length;i++){
		if(!obj[arr[i]]){
			array.push(arr[i]);
			obj[arr[i]]=1;
		}else{
			obj[arr[i]]++;
		}
	}
	return array;
}
//"null"去掉了

6、利用includes

function unique(arr){
	var array=[];
	for(var i=0;i<arr.length;i++){
		if(!array.includes(arr[i])){
			array.push(arr[i]);
		}
	}
	return array;
}
//{}没有去掉

7、hasOwnProperty

function unique(arr){
	var obj={};
	return arr.filter(function(item,index,arr){
		return obj.hasOwnProperty(typeof item+item)?false:(obj[typeof item+item]=true)
	})
}
//都去掉了

8、filter

function unique(arr){
	return arr.filter(function(item,index,arr){
		return arr.indexOf(item,0)===index;
	})
}
//都去掉了
手写promise.all和promise.race
  • promise.all
Promise.all = function (iterator) {  
    let count = 0//用于计数,当等于len时就resolve
    let len = iterator.length
    let res = []//用于存放结果
    return new Promise((resolve,reject) => {
        for(var item of iterator){
            Promise.resolve(item)//先转化为Promise对象
            .then((data) => {
                res[count++] = data
                if(count === len){
                    resolve(res)
                }
            })
            .catch(e => {
                reject(e)
            })
        }
    })
}
var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(function(values) {
  console.log(values);
});
  • promise.race
Promise.race = function (iterators) {  
    return new Promise((resolve,reject) => {
        for (const p of iterators) {
            Promise.resolve(p)
            .then((res) => {
                resolve(res)
            })
            .catch(e => {
                reject(e)
            })
        }
    })

}
var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, 'one');
});

var promise2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then(function(value) {
  console.log(value);
  // Both resolve, but promise2 is faster
});
模拟实现new
/*
* 1.创建一个空对象
* 2.链接到原型
* 3.绑定this值
* 4.返回新对象
*/
// 第一种实现
function createNew() {
    let obj = {}  // 1.创建一个空对象

    let constructor = [].shift.call(arguments)  
    // let [constructor,...args] = [...arguments]  

    obj.__proto__ = constructor.prototype  // 2.链接到原型

    let result = constructor.apply(obj, arguments)  // 3.绑定this值
    // let result = constructor.apply(obj, args)   

    return typeof result === 'object' ? result : obj  // 4.返回新对象
}

function People(name,age) {
    this.name = name
    this.age = age
}

let peo = createNew(People,'Bob',22)
console.log(peo.name)
console.log(peo.age)
实现call/apply/bind

call

Function.prototype.mycall = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('not funciton')
  }
  context = context || window
  context.fn = this
  let arg = [...arguments].slice(1)
  let result = context.fn(...arg)
  delete context.fn
  return result
}

apply

Function.prototype.myapply = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('not funciton')
  }
  context = context || window
  context.fn = this
  let result
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result
}

bind

// 思路:类似call,但返回的是函数
Function.prototype.mybind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  let _this = this
  let arg = [...arguments].slice(1)
  return function F() {
    // 处理函数使用new的情况
    if (this instanceof F) {
      return new _this(...arg, ...arguments)
    } else {
      return _this.apply(context, arg.concat(...arguments))
    }
  }
}
模拟实现Object.create()
// 思路:将传入的对象作为原型
function create(obj) {
  function F() {}
  F.prototype = obj
  return new F()
}
千分位分隔符

1、只适用于整数

//正则表达式
		function format (num) {
		    var reg=/\d{1,3}(?=(\d{3})+$)/g; 
		    return (num + '').replace(reg, '$&,');
		}
		function format(num){
			 num=num+'';//数字转字符串
			  var str="";//字符串累加
			  for(var i=num.length- 1,j=1;i>=0;i--,j++){
			      if(j%3==0 && i!=0){//每隔三位加逗号,过滤正好在第一个数字的情况
			          str+=num[i]+",";//加千分位逗号
			          continue;
			      }
			      str+=num[i];//倒着累加数字
			  }
			  return str.split('').reverse().join("");//字符串=>数组=>反转=>字符串
			}

2、小数部分不变

	let miliFormat = (() => {  
		let DIGIT_PATTERN = /(^|\s)\d+(?=\.?\d*($|\s))/g
		let MILI_PATTERN = /(?=(?!\b)(\d{3})+\.?\b)/g
		return (num) => num && num.toString()
			.replace(DIGIT_PATTERN, (m) => m.replace(MILI_PATTERN, ','))
	})()
在这里插入代码片
实现三角形
实现三栏布局/双栏布局

你可能感兴趣的:(前端面试,web前端,JS)