箭头函数、普通函数、构造函数
js构造函数详解
js中箭头函数和普通函数的区别
箭头函数特点:
1)箭头函数内部的this对象,就是函数上下文中的对象,而不是调用时的对象,不能用call,appply等方法改变this指向。
2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
3)不可以使用arguments对象,该对象在函数体内不存在,如果要用,可以用rest参数代替。
4)不可以使用yield命令,因此箭头函数不能用作Generator函数。
箭头函数用法:
1)使回调函数更简洁
2)搭配数组方法使用
不能使用的场景:需要用到函数方法中this的情况,不可作为构造函数使用
箭头函数没有函数名,不可以像普通函数一样作为构造函数使用,也不能用new创建实例类型。原因在于箭头函数中的this是固定的,在普通函数中this指向调用它的对象,而在箭头函数中,this永远指向其上下文。它的固定不是因为this绑定,是在于因为箭头函数没有自己的this,导致内部代码块的this就是外层代码块的this。因此箭头函数不能作为构造函数。箭头函数的 this 永远指向其上下文的 this ,任何方法都改变不了其指向,如 call() , bind() , apply()
构造函数通过“new 函数名”来实例化函数,任何的普通函数都可以作为构造函数使用,它们主要是从功能上来区分的。普通函数作为可以复用的代码,构造函数主要用来创建实例对象,特点是和new一起使用。在实践中通常使用首字母大写来区分。class 为构造函数的语法糖,即 class 的本质是构造函数,在执行时会转换为构造函数执行。
匿名函数
匿名函数是一个function关键字后没有标识符的函数表达式。通过匿名函数可以实现闭包。匿名函数模拟了块级作用域,执行完匿名函数后,存储在内存中的相应变量会被销毁。使用匿名函数可以减少全局变量,降低命名冲突。
缺点:调试困难,降低代码可读性。
this对象是在运行时基于函数执行环境绑定的,在全局函数中,this=window,在函数被作为某个对象的方法调用时,this等于这个对象。
匿名函数立即执行,也称为立即执行函数表达式(IIFE)
// 无参数的匿名函数(function () {console.log('zxx')})();// 推荐使用(function () {console.log('zxx')}())// 带参数的匿名函数(function (a, b, c) {console.log('参数一:', a) // 参数一: 这是普通函数传参的地方console.log('参数二:', b) // 参数二: 我是参数二console.log('参数三:', c) // 参数三: zxx})('这是普通函数传参的地方', '我是参数二', 'zxx')//匿名函数赋值let zxx = function (zxx) {console.log(zxx)}('zxx')
匿名函数用法
1.事件$('#zxx').onclick = function () {console.log('给按钮添加点击事件')}2.对象var obj = {name: 'zxx',zxx: function () {return this.name + ' is' + ' good girl'}}console.log(obj.zxx()) // zxx is good girl3.函数表达式var zxx = function () {return 'zxx is good girl'}console.log(zxx()) // zxx is good girl4.回调函数setInterval(function () {console.log('zxx is good girl')}, 1000)5.作为函数的返回值function zxx () {// 返回匿名函数return function () {return 'zxx'}}console.log(zxx()()) // zxx
new实现了什么
1、首先创一个新的空对象。
2、根据原型链,设置空对象的 _proto_为构造函数的 prototype ,并将this指向新创建的对象。
3、执行构造函数的代码(为这个新对象添加属性)。
4、返回这个对象。
对new理解:new 申请内存, 创建对象,当调用new时,后台会隐式执行new Object()创建对象。所以,通过new创建的字符串、数字是引用类型,而是非值类型。
new 被调用后大致做了哪几件事情?
📒 让实例可以访问到私有属性;
📒 让实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性;
📒 构造函数返回的最后结果是引用数据类型。
原型和原型链
为了将所有的对象关联起来,引入了prototype函数对象来包含所有实例对象的构造函数的属性和方法,引入了proto和原型链的概念解决继承的问题。
原型:构造函数的 prototype 属性指向了一个对象,它是调用该构造函数而创建的实例的原型,也就是原型对象,由实例对象的内置属性_proto_指向。每个原型都有一个 constructor 属性指向关联的构造函数。
原型用法:构造函数创建的实例可以访问实例的属性和方法,也可以访问原型的属性和方法。原型可以让所有实例对象共享原型包含的方法和属性,利用这个特性我们可以提取对象的公共属性和方法放在原型中。
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
原型链:子类构造函数的prototype的__proto__指向父类构造器的prototype,通过_proto_将原型对象链接起来。原型链是多个原型的关联,本质是一串顺序指向原型对象的指针列表。
原型链用法:当实例对象访问某个属性或方法时,首先会在自己的属性里查找,找不到就会沿着_proto_指针向上查找原型对象中的属性和方法。
问题:1、由于原型的属性和方法变成实例对象的共有属性和方法,因此对原型对象属性的修改会引起其他实例的变化。2、如果重写原型对象,相当于构造函数指定了新的原型对象,而已创建的实例的_proto_仍指向旧的原型,访问不到新原型的方法。
function SuperType(){this.colors = ["red", "blue", "green"];
}
SuperType.prototype.Fun = function(){};
function SubType(){
}
//继承了SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
JS中的继承
一篇文章理解JS继承
原型链继承、构造函数继承、组合继承、原型式继承、寄生继承、寄生组合继承。ES6中新增extends继承方法。
原型链继承,将父类的实例作为子类的原型。会共享引用属性。
构造函数继承,将父类构造函数的内容复制给了子类的构造函数。会独享所有属性,包括引用属性(重点是函数)。
组合继承,利用原型链继承要共享的属性,利用构造函数继承要独享的属性,实现相对完美的继承。
原型链继承与构造函数继承恰好相反,结合使用弥补缺点,用构造函数继承属性,用原型链继承函数,形成组合继承。
原型式继承,与原型链继承优缺点相同,本质是对象的浅复制。没有方法复用。
寄生组合继承:通过借用构造函数来继承属性,通过原型链形式来继承方法,复制父类构造函数的原型作为子类的原型,解决2次调用父类函数以及复用率的问题,是一种理想的继承方法。
ES6继承的结果和寄生组合继承相似,本质上,ES6继承是一种语法糖。使用Object.setPrototypeOf()。
class 为 构造函数的语法糖,即 class 的本质是 构造函数。class的继承 extends 本质 为构造函数的原型链的继承。
两者不同点:
ES6有子类到父类的原型链指向,而ES5中是构造函数复制,没有原型链指向。
ES6中子类实例基于父类实例构建,ES5不是。
静态方法、实例方法、原型方法
静态方法:静态方法属于类,实例对象不能调用。类的静态方法只能访问静态属性。
使用上:直接在类(构造函数)上定义,或通过static关键字在类(构造函数)内部定义。
实例方法:实例方法属于对象,只有在创建了实例的情况下才可以通过实例访问。
使用上:在类(构造函数)的实例上定义,在构造函数中通过this指针定义。
原型方法:实例和构造函数(通过prototype)都可以调用,是共享的方法。
使用上:通过类的prototype定义。
实例对象不能调用静态方法,类不能调用实例方法。
使用实例方法的目的是为了更好的面向对象。
//创建一个类(构造函数,构造函数是定义类的一个方法)
var Person=function(){console.log("person");
}
//创建这个类(构造函数)的静态方法
Person.say=function (){console.log("aaaaa");
}
Person.say();//aaaaa
//创建这个类(构造函数)的对象实例
var zym=new Person();
console.log(zym);//Person {}
//创建实例方法
zym.sayMe=function (){console.log("zym");
}
zym.sayMe();//zym
zym.say();//报错,实例方法无法访问静态方法//使用class和static关键字的静态方法
class Person2{static say(){console.log("person2");}
}
var zym2=new Person2();
console.log(zym2);//Person2 {}
Person2.say();//person2//使用prototype实现原型方法
Person2.prototype.sayAge=function (age){console.log("My age is "+age);
}
var zym3=new Person2();
zym2.sayAge(22);//My age is 22
zym3.sayAge(33);//My age is 33
Person2.prototype.sayAge(44);//My age is 44//通过this指针定义实例方法
var Person3=function (){
//this指向的是Person3的对象this.sayId=function (){console.log("111111");}
}
var zym4=new Person3();
zym4.sayId();//111111
Person3.sayId();//报错
this指向问题
箭头函数、new、bind、apply 和 call、欧比届点(obj.)、直接调用、不在函数里
this的指向不是创建的时候决定的,而是在调用时决定的,this永远指向的是最后调用它的对象。
var test={ name: "5555";test1:function(){console.log(this.name);console.log(this);}
}
test.test1();//结果'5555' test
var x=test.test1;
x();//结果undefined window
this指向函数运行时所在的环境。非严格模式this默认指向全局环境window,严格模式下this默认指向undefined。
普通函数中的this:this指向调用它的对象,如果没有实例化对象this指向window。
嵌套函数中的this:被多个外层对象调用,this指向的永远是上一级对象,如果没有则指向window。
构造函数中的this:如果构造函数返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this指向函数的实例。(this不能用在静态方法中,因为静态方法不是对象调用的,只能访问静态成员,访问不到非静态成员,所以没有this引用)
匿名函数中的this:因为匿名函数具有全局性,它的this一般指向window。使用call、apply、bind可以改变匿名函数this指向。
this四种绑定方法
默认绑定:在非严格模式下,默认绑定的this指向全局对象,严格模式下this指向undefined。
隐式绑定:函数在调用位置,是否有上下文对象,如果有,那么this就会隐式绑定到这个对象上。
显示绑定:通过apply、call、bind将函数中的this绑定到指定对象上。
new绑定:this会自动绑定在new期间创建的对象上。
this四种绑定方法,优先级:new关键字 > 显式绑定 > 隐式绑定 > 默认绑定
call、apply、bind
js基础-面试官想知道你有多理解call,apply,bind?
改变this指向:call、apply、bind
调用call/apply/bind的必须是个函数,可以理解为通过call/apply/bind借用方法。
fun.call(thisArg, param1, param2, ...)//返回fun执行的结果
fun.apply(thisArg, [param1,param2,...])//返回fun执行的结果
fun.bind(thisArg, param1, param2, ...)//返回fun的拷贝,并拥有指定的this值和初始参数
区别:
1、执行方面,call/apply改变了函数的this上下文后马上执行该函数,bind则是返回改变了上下文后的函数,不执行该函数;
2、返回值方面:call/apply 返回fun的执行结果,bind返回fun的拷贝,并指定了fun的this指向,保存了fun的参数。
call/apply用法:
1、判断数据类型,Object.prototype.toString.call();
2、类数组借用数组的方法;
3、apply获取数组最大值最小值;
4、构造函数继承,借用父类构造函数的方法,修改this指向,赋值父类的构造函数、属性到子类上。
作用域、作用域链
深入理解JS中的词法作用域与作用域链
作用域Scope:个人认为作用域是代码可访问的变量、函数和对象的区域。它决定了当前执行代码的访问权限。作用域有全局作用域、函数作用域和块级作用域。
全局作用域:全局作用域内的变量可以在代码的任意位置访问。一般有:最外层函数外定义的变量;未定义直接赋值的变量;window对象的属性。缺点:都可以访问,不够安全,容易命名冲突。
函数作用域:在函数的内部声明的变量在函数作用域内,不能在函数外部访问。随着函数调用创建,调用结束就销毁。
块级作用域:在一对{}中使用let和const声明的变量存在于该{}的块级作用域中,只在当前块中生效。
作用域链:把作用域层层嵌套,形成的关系叫做作用域链,它是查找变量的过程。一个执行环境的作用域,首先是自己的作用域,其次是父级作用域,最后是window全局作用域。
作用域与执行上下文:执行上下文在运行时确定,随时可能改变;js是静态作用域,作用域在解释时就确定,并且不会改变。
执行上下文、执行栈
说说执行上下文吧
执行上下文 为我们的可执行代码块提供了执行前的必要准备工作,例如变量对象的定义、作用域链的扩展、提供调用者的对象引用等信息。
全局执行上下文:不在函数内部的代码都在全局上下文中。他会创建一个全局的window对象,并设置this值等于这个对象。一个程序中只有一个全局上下文。
函数执行上下文:每一个函数被调用时都会为它创建自己的函数执行上下文。
Eval函数执行上下文:执行在 eval 函数内部的代码也会有它属于自己的执行上下文。
执行栈:是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。
每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。
闭包
面试官:说说作用域和闭包吧
闭包:能够访问另一个函数作用域中变量的函数。闭包的本质是利用了作用域的机制,来达到外部作用域访问内部作用域的目的。创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量。
闭包三个特点:
1.函数嵌套函数
2.函数外部可以引用内部的参数和变量
3.参数和变量不会被垃圾回收机制回收
缺点:常驻内存容易造成内存泄漏
优点:实现变量长期驻扎内存,避免全局变量的污染,私有成员的存在
避免内存泄露的几种方法:避免使用全局变量、谨慎地为DOM 绑定事件、避免过度使用闭包。最重要的,还是代码规范。
闭包应用:
单例模式:它保证了一个类只有一个实例,避免了重复实例化带来的内存开销。
实现私有属性:javascript 没有 java 中那种 public private 的访问权限控制,可以用闭包的手段来模拟出私有属性。
柯里化:是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回 接受余下的参数且返回结果的新函数 的技术。
例子:局部变量的累加
function outer(){var x=10;return function(){ //函数嵌套函数x++;alert(x);}
}
var y = outer(); //外部函数赋给变量y;
y(); //y函数调用一次,结果为11,相当于outer()();
y(); //y函数调用第二次,结果为12,实现了累加
在某个内部函数执行上下文创建时,会将它的父级函数的活动对象放在内部函数中,这样即使父级函数的执行上下文销毁,父级函数的活动对象仍然保存在内存中,可以被访问到,从而实现了闭包。
JS中的私有变量
私有变量:在函数中定义的变量,这些变量无法被外部访问。
特权方法:创建的用于访问私有变量的方法,称为特权方法。
JavaScript 的私有变量
构建私有变量的方式:
1、命名规范,通过在属性前加下划线命名的属性被认为是私有属性。例如_name,是基于开发者共识。
2、WeakMap,把拥有私有属性的对象作为WeakMap的键,使用一个函数创建存储私有属性的对象作为值。
3、Symbols,用法和WeakMap类似,把Symbol作为实例属性的键,为每个私有属性创建Symbol。
4、闭包,将数据封装在函数作用域内,作用域在函数调用时创建,在外部访问不到。(可以和WeakMap或Symbol一起使用)
5、代理,把对象封装在代理的对象中,使用代理拦截与该对象的交互。(基于命名规范)
JS执行机制
一次搞懂JS运行机制
Js 的事件循环(Event Loop)机制以及实例讲解
基础知识点{
进程是操作系统分配资源的最小单位,线程是程序执行的最小单位,一个进程由一个或多个线程组成。
进程之间相互独立,但同一进程下的各个线程间共享程序的内存空间。线程上下文切换比进程上下文切换要快得多。
浏览器是多进程的,它包含:渲染进程(重),GPU进程,Browser进程,第三方插件进程。渲染进程是多线程的,页面的渲染,JS的执行,事件的循环,都在渲染进程内执行。
JS是单线程的,它的主要用途是与用户互动,以及操作DOM,这决定了它只能是单线程。假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这会带来很复杂的同步问题。
但是浏览器是多进程的,Browser进程、GPU进程、渲染进程(重要)等。渲染进程包括GUI渲染线程、JS引擎线程、事件触发线程、定时器触发线程和异步http请求线程这几个重点线程。
}
事件循环
JS分为同步任务和异步任务,同步任务都在主线程(JS引擎线程)上执行,会形成一个执行栈,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放一个事件回调。一旦JS引擎线程空闲了,系统就会将任务队列中可运行的异步任务(即事件回调)添加到执行栈中执行。
JS引擎线程只会执行执行栈中的事件,执行栈中的代码执行完毕,就会读取任务队列中的事件并添加到执行栈中继续执行,这样反反复复就是事件循环(Event Loop)。
运行过程:首先,执行栈开始顺序执行。判断是否为同步,异步则进入异步线程,同步继续执行。执行栈空,询问任务队列中是否有事件回调。任务队列中有事件回调则把回调加入执行栈末尾继续从第一步开始执行。任务队列中没有事件回调则不停发起询问。
异步任务分为 宏任务(macrotask) 与 微任务 (microtask)
宏任务:将每次执行栈执行的代码当做是一个宏任务,每一个宏任务会从头到尾执行完毕,不会执行其他。
由于JS引擎线程和GUI渲染线程是互斥的关系,浏览器为了能够使它们任务有序的进行,会在一个宏任务执行结果后,在下一个宏任务执行前,GUI渲染线程开始工作,对页面进行渲染。
常见宏任务:script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)
微任务:微任务可以理解成在当前宏任务执行后立即执行的任务。
常见微任务:Promise、 MutaionObserver、process.nextTick(Node.js环境)
当一个宏任务执行完,会在渲染前,将执行期间所产生的所有微任务都执行完。
宏任务 -> 微任务 -> GUI渲染 -> 宏任务 -> …
完整的事件循环:
同步异步原理
前端面试:js同步与异步问题
同步任务
同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。就比如我们熟知的元素的渲染页面,其实就是一个同步任务。
异步任务
异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。比如js最基础的异步setTimeout,setInterval函数,就是异步,再比如打开页面时,图片的加载,其实也是一个异步任务。
js是同步的?
是的,单线程,那肯定只能同步(排队)执行咯
js为什么需要异步?
如果JS中不存在异步,只能自上而下执行,万一上一行解析时间很长,那么下面的代码就会被阻塞。
对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验
js单线程又是如何实现异步的呢?
通过事件循环(event loop) 实现’异步’
同步可以保证顺序一致,但是容易导致阻塞;异步可以解决阻塞问题,但是会改变顺序性,根据不同的需要去写你的代码。
nextTick
nextTick实现原理,必拿下!
vue 采用的异步更新策略,当监听到数据发生变化的时候不会立即去更新DOM,而是开启一个任务队列,并缓存在同一事件循环中发生的所有数据变更。
这种做法带来的好处就是可以将多次数据更新合并成一次,减少操作DOM的次数。
nextTick作用:
nextTick 接收一个回调函数作为参数,并将这个回调函数延迟到DOM更新后才执行;
使用场景:想要操作基于最新数据生成的DOM 时,就将这个操作放在 nextTick 的回调中;
执行机制:在vue中更新dom是异步的,当同步任务完成后才会去更新dom,nextTick会把此时的回调添加到异步任务队列中,当dom更新的任务完成后才会去执行nextTick函数
事件流
你真的理解事件绑定、事件冒泡和事件委托吗?
事件流,就是当触发某个元素的事件时,事件会按照DOM树结构进行传播,传播的过程分为捕获阶段、目标阶段和冒泡阶段三个阶段。
1、捕获阶段:按照DOM树结构由document对象向下的顺序传播,直到目标元素为止。
2、目标阶段:该阶段就是指目标元素触发当前事件。
3、冒泡阶段:按照DOM树结构由目标元素向上的顺序传播,直到 document对象为止。
解决事件冒泡
event.stopPropagation() 事件处理过程中,阻止了事件冒泡,但不会阻击默认行为(它就执行了超链接的跳转)
return false 事件处理过程中,阻止了事件冒泡,也阻止了默认行为(比如刚才它就没有执行超链接的跳转)
event.preventDefault() 事件处理过程中,不阻击事件冒泡,但阻击默认行为(a标签只执行所有弹框,却没有执行超链接跳转)
Vue.js为v-on提供了 事件修饰符,我们只需要添加click.stop即可防止事件冒泡
<!-- 阻止单击事件冒泡 -->
<a v-on:click.stop="doThis"></a>
深浅拷贝
JS 分为原始类型和引用类型,对于原始类型的拷贝,并没有深浅拷贝的区别,我们讨论的深浅拷贝都只针对引用类型。
浅拷贝和深拷贝都复制了值和地址,都是为了解决引用类型赋值后互相影响的问题。
但是浅拷贝只进行一层复制,深层次的引用类型还是共享内存地址,原对象和拷贝对象还是会互相影响。
深拷贝就是无限层级拷贝,深拷贝后的原对象不会和拷贝对象互相影响。
浅拷贝方法
Object.assign()方法效果类似于在数组中的concat拼接方法(浅拷贝数组还有slice方法),(其可以将源对象中可枚举属性进行复制到目标对象上,并返回目标对象,该方法中第一个参数便就是目标对象,其他参数为源对象。)
深拷贝方法
1、使用JSON.parse(JSON.stringify())实现,JSON 中的 stringify 会把一个 JavaScript 对象序列化为一个 JSON 字符串,而 parse 又会把 JSON 字符串反序列化为一个 JavaScript 对象,通过这两种方法的结合,我们就可以实现一个简单的深拷贝。
- 无法拷贝一些特殊对象,如:RegExp、Date、Set、Map 等。
- 当对象中有undefined类型或function类型的数据时,数据会丢失
- 当对象属性值为NaN、Infinity和-Infinity的时候,会变成null
- 循环引用报错
2、递归方式实现深拷贝,使用Weak Map解决循环引用问题,WeakMap是弱引用类型,弱引用在回收机制上比强引用好,在“适当”的情况将会被回收,减少内存资源浪费,可以防止内存泄露。另外还可以处理特殊对象的拷贝。
3、使用第三方库lodash中的cloneDeep()方法
4、JQuery的extend()方法进行深拷贝(推荐在JQ中使用)
手写深拷贝
如何写出一个惊艳面试官的深拷贝?
浏览器渲染
浏览器工作原理
从上面这个图上,我们可以看到,浏览器渲染过程如下:
1、解析HTML,生成DOM树,解析CSS,生成CSS规则树
2、将DOM树和CSS规则树结合,生成渲染树(Render Tree)
3、Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
4、Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
5、Display:将各层像素信息发送给GPU,GPU将各层合成(composite)展示在页面上。(这一步其实还有很多内容,比如会在GPU将多个合成层合并为同一个层,并展示在页面中。)
回流(重排)和重绘
你真的了解回流和重绘吗
重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。 触发重绘的条件: 改变元素外观属性。如: color, background-color等。
回流是指当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。每个页面至少需要一次回流,就是在页面加载的时候。
避免重绘和回流
- 避免使用table布局。
- 避免设置多层内联样式。
- 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
- 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
- 使用createDocumentFragment进行批量的 DOM 操作。
- 对于 resize、scroll 等进行防抖/节流处理。
- GPU加速,动画使用transform或者opacity实现。
- 将元素的will-change 设置为 opacity、transform、top、left、bottom、right 。这样子渲染引擎会为其单独实现一个图层,当这些变换发生时,仅仅只是利用合成线程去处理这些变换,而不牵扯到主线程,大大提高渲染效率。
防抖和节流
在前端开发中会遇到频繁的事件触发,为了把事件执行控制在合理范围内,一般会使用两种方法,防抖和节流。
防抖:是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。需要延时器辅助实现,延迟需要执行的代码,如果触发就把延时记录清除,重新计时,直到即使完毕没有方法触发,就执行代码。
节流:是指一定时间内js方法只执行一次。记录当前代码是否正在执行,如果在执行就取消执行直接return,如果空闲就正常执行。
debounce
search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
throttle
鼠标不断点击触发,mousedown(单位时间内只触发一次)
监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
前端面试复习
严格模式和非严格模式
- 严格模式通过抛出错误来消除了一些原有静默错误。
- 严格模式修复了一些导致JavaScript引擎难以执行优化的缺陷:有时候,相同的代码,严格模式可以比非严格模式下运行得更快。
- 严格模式禁用了在ECMAScript的未来版本中可能会定义的一些语法。
区别:
严格模式变量必须声明;
严格模式禁用with;
严格模式禁止this指向全局变量;
严格模式无法删除变量;
严格模式禁止八进制写法。
js中的循环(for in for of)
forEach 遍历列表值,不能使用 break 语句或使用 return 语句
for in 遍历对象键值(key),或者数组下标,不推荐循环一个数组
for of 遍历列表值,允许遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构等.在 ES6 中引入的 for of 循环,以替代 for in 和 forEach() ,并支持新的迭代协议。
for in循环出的是key,for of循环出的是value;
for in可以遍历对象的所有属性,包括通过原型链继承的属性,需要使用hasOwnProperty() 过滤;
for of是ES6新引入的特性。修复了ES5的for in的不足;
for of不能循环普通的对象,需要通过和Object.keys()搭配使用。
异步
回调和Promise
理解回调函数与 Promise 原理
回调缺点:
嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身(回调地狱
嵌套函数一多,就很难处理错误
回调函数態使用try…catch…捕获错误,不能直接return(错误难以捕捉
(并行问题
Promise中的特性
Promise不会??看这里!!!史上最通俗易懂的Promise!!!
Promise 是异步编程的一种解决方案:
从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。
promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态);状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
解决问题:
1、回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象
2、promise可以支持多个并发的请求,获取并发请求中的数据,promise可以解决异步问题,本身不能说promise是异步的
Promise三种状态
all和race区别
Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。
async、await
async function用来声明一个异步函数,这个异步函数返回一个Promise
await后面接一个会返回Promise的函数并执行这个函数,await会暂停async代码的执行,直到Promise执行完
使用async、await会让异步函数的语法和结构更像同步函数
使用try-catch来做错误捕捉
必须等内部所有await命令后面的Promise对象执行完,才会发生状态改变,或者遇到return语句或者抛出错误
只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数
使用 async和await 的注意事项:
1.await命令后面的Promise对象,运行结果可能是rejected,最好把await命令放在try catch中
2.多个await命令后面的异步操作,如果不存在继发关系(互相不依赖),最好让它们同时触发,并发
let foo = await getFoo();
let bar = await getBar();
等价于
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
3.await命令只能用在async函数之中,如果用在普通函数,就会报错
Generator
Generator 可以看做是一个更加灵活的 Iterator ,他们之间是可以互相替代的,但是, Generator 由于可以通过 yield 随时暂停,因此可以很方便进行流程控制和状态管理。
Async/Await 是 Generator 的一个语法糖:
async 对应的是 *
await 对应的是 yield
它自动帮我们进行了 Generator 的流程控制,我们不用手动在ajax成功的时候手动调用 next。
- Iterator 是一个循环接口,任何实现了此接口的数据都可以被 for in 循环遍历
- Generator 是一个可以暂停和继续执行的函数,他可以完全实现 Iterator的功能,并且由于可以保存上下文,他非常适合实现简单的状态机。另外通过一些流程控制代码的配合,可以比较容易进行异步操作。
- Async/Await 就是generator进行异步操作的语法糖。
垃圾回收机制
标记清除(Mark-Sweep),目前在 JavaScript引擎 里这种算法是最常用的,分为 标记 和 清除 两个阶段,标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁。
优点:简单
缺点:内存碎片化,清除之后剩余的对象位置不变而导致的空闲内存不连续,可以使用 标记整理法
引用计数(Reference Counting),这其实是早先的一种垃圾回收算法,它把 对象是否不再需要 简化定义为 对象有没有其他对象引用到它,如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。
优点:垃圾立即回收
缺点:需要计数器,循环引用问题占用内存
「硬核JS」你真的了解垃圾回收机制吗
ES6新特性
ES6十大新特性
let const
模板字符串
箭头函数
class类的使用
for of和for in
参数默认值(可以在函数小括号中定义默认值)
二进制和八进制字面量
Spread/Rest操作符(…)
对象和数组解构(let {name,id,age}=person)
跨域
同源策略:
如果两个页面的协议、域名和端口都一样,则两个页面具有相同的源。
同源策略(Same Origin Policy)是一种约定,它是浏览器最核心也是最基本的安全功能。通俗理解: 浏览器规定,A网站的JavaScript,不允许和非同源的网站C之间,进行资源交互。
例如:
1.无法读取非同源网页的cookie,localstorage和indexedDB
2.无法接触非同源网页的DOM
3.无法向非同源地址发送Ajax请求
跨域:
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。
解决跨域
1、cors跨域资源共享,cors允许浏览器向跨源服务器发出XMLHttpRequest请求,浏览器要跨域访问服务器的资源,需要获得服务器的允许,只要服务器实现cors接口,就可以实现跨域请求。浏览器在头信息中增加一个Origin字段,从而发出cors请求。要支持cors访问需要服务器在响应头中添加Access-Control-Allow-Origin。分为简单请求、需要预检options的请求和附带身份凭证的请求。
2、jsonp通过动态生成script标签,利用script元素请求可以跨域的特点,把数据作为参数传递。script标签的src属性加载资源时不受同源策略的影响的这一特性,并且script会将引用的外部文件的文本内容当做js代码来进行解析。它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
3、代理服务器,proxy正向代理,nginx反向代理(正向代理代理客户端,反向代理代理服务器。)并不是网页服务访问代理,而是代理检测网页服务内部的接口服务,当符合条件的服务出现的时候,代理拦截住,并替代网页服务返回结果。nginx通过proxy_pass实现了反向代理跨域(即这种方法本身是不需要ACA-系列header的),其本质也是使得请求地址和服务器同域,此时前端相当于不跨域。
4、使用websocket,当客户端与服务端创建WebSocket连接后,本身就可以天然的实现跨域资源共享,WebSocket协议本身就不受浏览器“同源策略”的限制。(原因:Cookie的存在就是为了给无状态的HTTP协议通讯添加状态,因为Cookie是明文传输的,且通常包含用户的身份信息,所以非常受到网络攻击者的“关注”。但是想想WebSocket协议下的通讯机制,客户端和服务端一旦建立连接,就可以顺畅的互发数据,因此WebSocket协议本身就是“有状态的”,不需要Cookie的帮忙,既然没有Cookie,自然也不需要“同源策略”去保护,因此其实这个问题也不成立。)
页面性能优化
前端性能优化-页面加载渲染优化
- 减少HTTP请求
- 合并js和css文件,webpack
- 浏览器缓存,强缓存、协商缓存
- 加快请求速度
- 图片压缩
- 预解析DNS
- CDN分发
- 降低请求量
- 懒加载
- 缓存
- 浏览器缓存
- 本地存储,Cookie存储登录状态,Local Storage,Session Storage
- 渲染优化
- js/css代码优化,将 CSS 放在 head 标签里,启用 CDN 实现静态资源加载速度的优化,使用异步模式加载js减少DOM阻塞
- 防抖节流
vue页面优化
Vue 项目性能优化 — 实践指南(网上最全 / 详细)
打包优化:
- 路由懒加载,把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件。
- 按需加载,只引入需要的组件,减小项目体积。
- 通过配置webpack把一些平常不需要用的包排除在打包文件之外,如vue、element。进行tree shaking,压缩代码。将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存。
- 配置CDN加速,静态资源都上传到 CDN,可以极大地提高访问速度。
- 对 css、js、图片等资源进行压缩,并且服务器开启 GZIP 压缩。
- 浏览器缓存,强缓存,协商缓存。
CDN(Content Delivery Network),即内容分发网络,构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
首屏白屏解决
vue页面白屏的原因及优化
1、路由懒加载
2、CDN资源分发
3、GZIP压缩资源
4、SSR服务端渲染
5、骨架屏,loading
强缓存、协商缓存
HTTP中的强缓存与协商缓存
强缓存
强缓存不会向服务器发送请求,直接从缓存中读取资源,在 chrome 控制台的 network 选项中可以看到该请求返回 200 的状态码,并且size显示from disk cache或from memory cache;
协商缓存
协商缓存会先向服务器发送一个请求,服务器会根据这个请求的 request header 的一些参数来判断是否命中协商缓存,如果命中,则返回 304 状态码并带上新的 response header 通知浏览器从缓存中读取资源。
在 HTTP 中,我们可以通过设置响应头以及请求头来控制缓存策略。
强缓存可以通过设置Expires和Cache-Control 两种响应头实现。如果同时存在,Cache-Control优先级高于Expires。
协商缓存可以通过 Last-Modified/If-Modified-Since和ETag/If-None-Match这两对 Header 来控制。
TypeScript
TS和JS区别
Typescript和Javascript之间的区别
TypeScript 是JavaScript 的超集,即包含JavaScript 的所有元素,能运行JavaScript 的代码,并扩展了JavaScript 的语法。相比于JavaScript ,它还增加了静态类型、类、模块、接口和类型注解方面的功能,更易于大项目的开发。
TS泛型
轻松拿下 TS 泛型
使用泛型,可以在定义函数、接口或类的时候,不预先指定具体类型,而是在使用的时候再指定类型。
用法:
- 泛型约束类:定义一个栈,有入栈和出栈两个方法,想让入栈和出栈的元素类型统一。
- 泛型约束接口:使用泛型,也可以对接口进行改造,让 接口更灵活。
- 泛型定义数组
面向对象
面向对象基础
面向对象和面向过程的区别
两者的主要区别在于解决问题的方式不同:
面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。
另外,面向对象开发的程序一般更易维护、易复用、易扩展。
面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发。
面向对象三大特征
封装:封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
继承:类从父类中继承了属性和方法。可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
多态:表示一个对象具有多种的状态,父类的引用指向了子类的对象,不同类型的对象针对相同的方法,产生了不同的行为。
WebPack相关
Webpack面试题
当面试官问Webpack的时候他想知道什么
webpack优化前端性能:
webpack 十连问你能接住几题
webpack是一个模块打包工具。通过loader转换文件,通过plugin使用钩子,最后输出多个模块组合成的文件。(webpack的目的是将项目打包为多个模块组成的文件)
webpack的作用其实有以下几点:
模块打包、编译兼容loader、能力扩展plugins
webpack工作方式:把项目当作一个整体,从指定的主文件入手,找到项目所有的依赖文件,通过loaders处理它们,最后打包为浏览器可执行的一个或多个js文件。
loader:模块转换器,webpack原生只能解析js文件,loader让webpack拥有加载解析非js文件的能力。
plugin:扩展插件,监听webpack生命周期中的事件,参与到打包流程改变输出结果。
Webpack的loader和plugin有何区别?
loader是一个转换器,可以将A文件编译成B文件,主要用于将非js模块转换成为js模块,例如,将less通过less-loader转换成为css,通过css-loader进行css引用处理等,最后通过style-loader将css转换为脚本加载的js文件。
plugin是一个扩展器,监听webpack打包过程中的某些节点,执行更为广泛的任务。从性能优化到代码压缩,从定义环境变量到重写html文件,功能强大到可以用来处理各种各样的任务。
构建流程:初始化参数->开始编译->确定入口->编译模块->完成模块编译->输出资源->输出完成。
webpack的整个打包流程:
1、读取webpack的配置参数;
2、启动webpack,创建Compiler对象并开始解析项目;
3、从入口文件(entry)开始解析,并且找到其导入的依赖模块,递归遍历分析,形成依赖关系树;
4、对不同文件类型的依赖模块文件使用对应的Loader进行编译,最终转为Javascript文件;
5、整个过程中webpack会通过发布订阅模式,向外抛出一些hooks,而webpack的插件即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的。
entry:入口,告诉webpack使用哪个文件为构建项目的起点。
output:出口,告诉webpack在哪里输出打包好的代码及命名。
热更新:可以使得代码修改后不需要刷新浏览器就可以更新模块。
优化前端性能:优化webpack的输出结果,让打包的结果在浏览器运行高效快速。
1压缩代码,2利用CDN加速,3删除死代码,4提取公共代码
webpack import() 按需加载模块
Webpack 理解 Chunk
module
首先来说module,Webpack可以看做是模块打包机,我们编写的任何文件,对于Webpack来说,都是一个个模块。所以Webpack的配置文件,有一个module字段,module下有一个rules字段,rules下有就是处理模块的规则,配置哪类的模块,交由哪类loader来处理。
chunk
Chunk是Webpack打包过程中,一堆module的集合。我们知道Webpack的打包是从一个入口文件开始,也可以说是入口模块,入口模块引用这其他模块,模块再引用模块。Webpack通过引用关系逐个打包模块,这些module就形成了一个Chunk。
如果我们有多个入口文件,可能会产出多条打包路径,一条路径就会形成一个Chunk。
Git相关
前端面试题汇总-git篇
git和svn有什么区别:git是分布式的,svn是集中的版本控制工具。
pull和fetch区别:git pull=git fetch+git merge
git pull:相当于是从远程获取最新版本并merge到本地
git fetch:相当于是从远程获取最新版本到本地,不会自动merge
fork:复刻,对存储仓库进行的远程的服务器端的拷贝。
clone:克隆,对某个远程仓库的本地拷贝,包括所有历史记录和分支。
branch:分支,处理某存储仓库的变更,最终用于与其他代码部分的合并。
stash:把未提交的修改保存以供后续使用,以后就可以从工作副本中进行还原。
我在工作中是如何使用 git 的
Git发布项目流程
1、创建git项目
2、git init 初始化git项目
3、git checkout -b master 新建一个master分支
4、git add . 暂存代码
5、git commit -m “XXX” 提交代码到本地
6、本地仓库连接github远程库 git remote ……
7、git push -u origin master 提交项目到远程的master分支
HTML
CSS3 和 HTML5 新特性一览
html篇–这可能是目前较为全面的html面试知识点了吧
HTML语义化
语义化使元素本身传达了关于标签所包含内容类型的一些信息。比如header、footer、main、aside、details、article等。
- 页面结构:使页面没有css的情况下,也能够呈现出很好的内容结构
- 有利于SEO:爬虫依赖标签来确定关键字的权重,因此可以帮助爬虫抓取更多的有效信息
- 提升用户体验:例如title、alt可以用于解释名称或者解释图片信息,以及label标签的灵活运用。
- 便于团队开发和维护:语义化使得代码更具有可读性,让其他开发人员更加理解你的html结构,减少差异化。
- 方便其他设备解析:如屏幕阅读器、盲人阅读器、移动设备等,以有意义的方式来渲染网页。
语义化标签和布局
H5语义化标签和布局
HTML文档内容
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>文档标题</title></head><body><div id="app"></div></body>
</html>
- !DOCTYPE html 标记了文档类型。早期的DOCTYPE用来标记一些html编写规范,现今基本使用html5,仅使用 !DOCTYPE html 保证文档正确读取,其他知识点不再需要。
- html 是根元素
- head 可以理解为本文件的配置文件,包含meta、title、link等标签,声明了面向搜索引擎的搜索关键字、css文件、编码声明等。
- meta charset=“utf-8” 指定文档使用utf-8编码,该编码已经包括绝大多数人类语言的字符,已没有必要使用其他编码。
- title 页面标题,显示在浏览器标签页上。
- body元素,包含所有希望用户看到的内容,包括文本、图像、视频等。
script标签
图解 script 标签中的 async 和 defer 属性
iframe
iframe用于在当前 HTML 页面中嵌入另一个文档的,且所有主流浏览器都支持iframe标签。
使用iframe的优缺点
用法:
嵌入网页编辑器,页面内展示excel,pdf,加载广告
缺点:
不安全;(不安全)
SEO不友好;(搜不到)
会产生很多页面,不易管理;(页面多)
浏览器无法使用后退按钮;(退不了)
增加服务器的http请求,造成拥塞;(请求多)
title和alt
alt 和 title 是两个常用的属性,alt定义元素的替换文本,title定义元素的提示文本。
当图片无法显示的时候,必须准备替换的文本来替换无法显示的图片,给用户提供文字说明,使得用户了解图像信息,起到替代图像的作用,这对于图像和图像热点是必须的。(必填属性)
alt 属性只能用在 img、area、input元素中(包括applet元素–已废弃)。
–对于input元素,alt属性用来替换提交按钮的照片
–对于area替换可点击区域的图片
标签定义图像映射内部的区域(图像映射指的是带有可点击区域的图像)。 元素始终嵌套在
title不仅是属性,还可以作为标签
title作为标签时,其内容就是标题栏上的内容
title 作为属性时,可以为元素提供额外信息,可以作为所有元素的属性。(非必填)
例如给p标签添加属性title,把鼠标移动到该段落的文字上面,就会显示title内容,起到补充说明和提示的效果
CSS
盒模型
css的盒模型由里到外包括:content(内容)、padding(内边距)、border(边框)、margin(外边距)四个部分。
css盒模型有两种:标准模型(浏览器默认)+ IE模型
标准盒子模型(content-box):元素的宽度width=内容宽度
IE盒子模型(border-box):元素的宽度width=内容宽度 + padding + border
盒模型设置 box-sizing
//在不设置box-sizing的情况下,box-sizing 默认是(标准盒子)content-box ./* 标准模型 */
box-sizing:content-box;/*IE模型*/
box-sizing:border-box;
属性优先级
同一属性优先级:
内联 > ID选择器 > 类选择器 > 标签选择器
优先级是由 A 、B、C、D 的值来决定的,其中它们的值计算规则如下:
如果存在内联样式,那么 A = 1, 否则 A = 0;
B 的值等于 ID选择器 出现的次数;
C 的值等于 类选择器 和 属性选择器 和 伪类 出现的总次数;
D 的值等于 标签选择器 和 伪元素 出现的总次数 。
块级元素、行内元素、行内块元素
CSS中 块级元素、行内元素、行内块元素区别
元素类型转换 display
- display:block ,定义元素为块级元素。div,h1-6,ul,ol,li,iframe,table,header,footer,article
- display : inline ,定义元素为行内元素。a,strong,span,small
- display:inline-block,定义元素为行内块级元素。button,input,select,img,textarea
不管块级元素还是行内元素,区别:一是排列方式,二是宽高边距设置,三是默认宽度。
块级元素会独占一行,而内联元素和内联块元素则会在一行内显示;
块级元素和内联块元素可设置 width、height 属性,而内联元素设置无效;
块级元素的 width 默认为 100%,而内联元素则是根据其自身的内容或子元素来决定其宽度;
而行内块级元素又同时拥有块级元素和行内元素的特点。
伪类/伪元素
彻底搞懂 CSS 伪类和伪元素
- 伪类:以冒号(:)开头,用于选择处于特定状态的元素。
1.动态伪类::visited、:focus、:hover等
2.状态伪类::disabled、:empty、:required 等
3.结构伪类::first-child、:nth-of-type等
4.其他伪类::target、:lang、:not()等 - 伪元素:以双冒号(::)开头,用于在文档中插入虚构的元素。
::first-letter 选中块状元素中的首字母
::first-line 选中首行
::before 在之前创建一个不在文档树中的元素
::after 在之后创建一个不在文档树中的元素
::placeholder 选中表单元素的占位文本
::file-selector-button 选中类型为 file 的 input 里面的 button
::selection 选中被用户高亮的部分
::backdrop 选中视觉聚焦元素后面的背景元素
::marker 选中 list 的 marker
伪类用于向某些已经存在的选择器添加特殊效果(当状态改变时)
伪元素用于将特殊效果添加到不存在的虚拟元素中(浏览器自动创建)
也就是说伪类的本质还是类(class),作用于标签本身,只不过限定了状态条件;而伪元素的本质是元素(element),作用于该虚拟元素的内容本身。
css单位(vw、百分比、em、rem)
vw 和 百分比的区别
vw就是view width,可视宽度,所以一般来说都是可视窗口的一半。
百分比的宽度,就是自己父元素宽度的一半了。
em是一个相对单位。相对于当前对象内文本的font-size,如果当前文本的字体尺寸没有设置,则相对于浏览器的默认字体尺寸。即1em=16px
rem是一个相对单位。是相对HTML根元素。
em和rem的区别
rem是相对于根元素进行计算,而em是相对于当前元素或父元素的字体大小。
rem不仅可以设置字体的大小,还支持元素宽、高等属性。
em是相对于当前元素或父元素进行换算,层级越深,换算越复杂。而rem是相对于根元素计算,避免层级关系。
css画三角
用 css 画三角形、梯形、扇形、箭头和椭圆几种基本形状
CSS如何画一个三角形?原理是什么
原理分析
可以看到,边框是实现三角形的部分,边框实际上并不是一个直线,如果我们将四条边设置不同的颜色,将边框逐渐放大,可以得到每条边框都是一个梯形
当分别取消边框的时候,发现下面几种情况:
- 取消一条边的时候,与这条边相邻的两条边的接触部分会变成直的
- 当仅有邻边时, 两个边会变成对分的三角
- 当保留边没有其他接触时,极限情况所有东西都会消失
通过上图的变化规则,利用旋转、隐藏,以及设置内容宽高等属性,就能够实现其他类型的三角形
弹性盒 flex
浅谈Flex布局的属性及使用
Flex 是 Flexible Box 的缩写,意思为“弹性布局”,是一种当页面需要适应不同的屏幕大小以及设备类型时确保元素拥有恰当的行为的布局方式。
引入弹性盒布局模型的目的是提供一种更加有效的方式来对一个容器中的子元素进行排列、对齐和分配空白空间。
Flex布局的特点:
块级布局侧重垂直方向、行内布局侧重水平方向,而Flex布局是与方向无关的
Flex布局可以实现空间自动分配、自动对齐(弹性、灵活)
Flex布局适用于简单的线性布局,更复杂的布局使用Grid布局
开启方式:在父级元素上写上display: flex;
position
position属性有4种取值static、fixed、relative、absolute,其区别是:
1、static:静态定位,是position属性的默认值,表示无论怎么设置top、bottom、right、left属性元素的位置(与外部位置)都不会发生改变。
2、relative:相对定位,表示用top、bottom、right、left属性可以设置元素相对与其相对于初始位置的相对位置。
3、absolute:绝对定位,表示用top、bottom、right、left属性可以设置元素相对于其父元素(除了设置了static的父元素以外)左上角的位置,如果父元素设置了static,子元素会继续追溯到祖辈元素一直到body。
4、fixed:固定定位,相对于浏览器窗口进行定位
5、sticky:粘性定位,它相当于relative和fixed的结合,在它本来的位置时就是relative,保留自身位置。当超出浏览器高/宽度时又变为fixed。
BFC
面试官:请说说什么是BFC?
前端人员不要只知道KFC,你应该了解 BFC、IFC、GFC 和 FFC
BFC 全称:Block Formatting Context, 名为 “块级格式化上下文”。
简单来说就是,BFC是一个完全独立的空间(布局环境),让空间里的子元素不会影响到外面的布局。
1、会有外边距合并的问题,
2、会计算浮动元素的高度,
3、bfc内部不会影响bfc外部。
怎样触发BFC
浮动元素
脱离标准流的定位
overflow不为visible的元素
这里简单列举几个触发BFC使用的CSS属性
float: left/right/inherit
overflow: hidden/scroll/auto/inherit
position: absolute/fixed
display: inline-block
display: table-cell
display: flex
BFC的规则
- BFC就是一个块级元素,块级元素会在垂直方向一个接一个的排列
- BFC就是页面中的一个隔离的独立容器,容器里的标签不会影响到外部标签
- 垂直方向的距离由margin决定, 属于同一个BFC的两个相邻的标签外边距会发生重叠
- 计算BFC的高度时,浮动元素也参与计算
BFC解决了什么问题
- 使用Float脱离文档流,高度塌陷
- 解决块级元素垂直方向margin重叠
- 两栏布局,其中一栏Float浮动覆盖另一栏宽度
水平垂直居中
CSS 实现元素水平垂直居中的 N 种方式
左侧固定右侧自适应
前端学习笔记 CSS基础篇-左侧固定,右侧自适应(或右侧固定,左侧自适应)布局方法
- 方法一:左侧div浮动 右侧元素设置margin-left自适应
- 方法二:左侧div浮动,右侧元素创建BFC,overflow:hidden
- 方法三:左侧div采用绝对布局,右侧元素设置margin-left自适应
- 方法四:外层父级元素使用table布局,子元素使用table-cell
- 方法五:双float + calc计算宽度 方法六:flex布局
清除浮动
1、设定高度
2、clear属性清除浮动子元素下设置空盒子,然后空盒子clear:both;
clear属性不允许被清除浮动的元素的左边/右边挨着浮动元素,底层原理是在被清除浮动的元素上边或者下边添加足够的清除空间。
3、设置BFC清除,为父元素添加overflow
4、伪元素标签法清除浮动
css预处理器
css预处理器的概念:CSS预处理器用一种专门的编程语言,进行Web页面样式设计,然后再编译成正常的CSS文件,以供项目使用。CSS预处理器为CSS增加一些编程的特性,无需考虑浏览器的兼容性问题。
Sass是一种动态样式语言,Sass语法属于缩排语法,比css比多出好些功能(如变量、嵌套、运算,混入(Mixin)、继承、颜色处理,函数等),更容易阅读。
Less也是一种动态样式语言. 受Sass影响较大,对CSS赋予了动态语言的特性,如变量,继承,运算, 函数. Less 既可以在客户端上运行 (支持IE 6+, Webkit, Firefox),也可在服务端运行 (借助 Node.js)。
SASS 变量符是 $;提供输出设置;支持条件语句
LESS 变量符是 @;无输出设置;不支持条件语句
Vue
30 道 Vue 面试题,内含详细讲解(涵盖入门到精通,自测 Vue 掌握程度)
生命周期
父子组件生命周期执行顺序
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
MVVM
MVVM -
Model View ViewModel
,它包括 DOM Listenters 和 Data bindings,前者实现了页面与数据的绑定,当页面操作数据的时候 DOM 和 Model 也会发生相应的变化。后者实现了数据与页面的绑定,当数据发生变化的时候会自动渲染页面。
MVVM和MVC
1、MVVM 实现了数据与页面的双向绑定,MVC 只实现了 Model 和 View 的单向绑定。
2、MVVM 实现了页面业务逻辑和渲染之间的解耦,也实现了数据与视图的解耦,并且可以组件化开发。
3、MVC需要大量的DOM操作,而MVVM中自动维护Model中的变化和View同步,减少操作DOM。
Vue双向绑定(v-model)
我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,v-model 本质上不过是语法糖,它负责监听用户的输入事件以更新数据。v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
text 和 textarea 元素使用 value 属性和 input 事件;
checkbox 和 radio 使用 checked 属性和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。
v-model 是双向绑定,单向数据流。子组件不能改变父组件传递给它的 prop 属性,推荐的做法是它抛出事件,通知父组件自行改变绑定的值。
v-model 副作用如下:如果 v-model 绑定的是响应式对象上某个不存在的属性,那么 vue 会悄悄地增加这个属性,并让它响应式。
响应式原理
学透Vue源码
用自己的话总结vue双向绑定数据原理
不要再搞混Vue的响应式原理和双向数据绑定了
vue是采用数据劫持结合观察者模式的方式,通过劫持各个属性的setter,getter,在数据变动时发布消息给观察者,触发响应的监听回调。
Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。descriptor是要定义或修改的属性描述符,其中存取描述符是由 getter 函数和 setter函数所描述的属性。在vue2.x中,要想实现data中所有属性都实现数据劫持,就要先遍历data中的所有属性,对每一个属性都使用Object.defineProperty,当属性的值发生变化时,就执行视图渲染操作。由于 Object.defineProperty 每次只能设置一个具体的属性,因此需要进行递归遍历操作,如果数据层级很深,就会造成性能隐患。Object.defineProperty 只能应用在对象上,不能用于数组。(对数组的数据劫持,通过修改数组原型对象的方法)只能够监听定义时的属性,不能监听新加的属性。
在vue3版本中,使用了proxy去实现对象的监听,避免了Object.defineProperty问题的出现。Proxy 用于创建一个对象的代理,从而实现基本操作的拦截和自定义。
对比
Proxy代理的是整个对象,而不是对象的某个特定属性,不需要我们通过遍历来逐个进行数据绑定,因此解决了Object.defineProperty的不足:
- 对于一个对象,不需要要遍历来对所有属性监听
- 对于对象的新增属性,也能监听
- 对于数组通过push、unshift等操作方法和索引操作,也能监听
访问不存在的属性,仍然可以被get拦截到- 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改
vue中数据代理
通过vm对象代理data中的数据,更加方便操作
通过Object.defineProperty()把data对象中的所有属性添加到vm上
为每一个添加到vm上的属性都指定一个getter/setter
在getter/setter内部操作data中对应属性
vue常用指令/自定义指令
Vue常用指令(大全)
v-bind: 动态绑定一个或多个属性,v-bind:title=“mytitle” 缩写 :title=“mytitle”
v-on: 主要用来监听dom事件,以便执行一些代码块。表达式可以是一个方法名。 v-on:click=“alert(‘hello’)” 缩写 @click=“alert(‘hello’)”
v-model: 这个指令用于在表单上创建双向数据绑定。
v-for: 根据遍历数组来进行渲染
v-if/v-show: 用于根据条件展示元素。
watch和computed区别
- 功能上:computed是计算属性,watch是监听一个值的变化,然后执行对应的回调。
- computed支持缓存,相依赖的数据发生改变才会重新计算;watch不支持缓存,只要监听的数据变化就会触发相应操作。
- 是否调用return:computed中的函数必须要用return返回,watch中的函数不是必须要用return。
- computed默认第一次加载的时候就开始监听;watch默认第一次加载不做监听,如果需要第一次加载做监听,添加immediate属性,设置为true(immediate:true)。
- computed:一个属性受多个属性影响。watch:一条数据影响多条数据。
- computed不支持异步,当computed内有异步操作时是无法监听数据变化的;watch支持异步操作。
Vue3中的组件通信
组件化可以重复使用,提高开发效率,提升项目木可维护性,更好地协同多人开发。
1、v-bind/props,父组件向子组件传值;(:)
2、v-on/emit,绑定事件监听,适用于子传父;(@)
3、v-model,语法糖,是前两种结合;
4、refs,通过ref的方式获取组件或者元素,要定义同名的ref对象,在组件挂载;
5、provide/inject是Vue中提供的一对API,可以实现父组件向子组件传递数据。
6、EventBus,事件总线(Vue3中无),Vue官方推荐mitt或tiny-emitter,原理一样;
7、Vuex,状态管理工具。
Vue样式隔离scoped
当 style 标签加上 scoped 属性时,scoped 会在 DOM 结构及 css 样式上加上唯一性的标记 data-v-xxx 属性,从而达到样式私有化,不污染全局的作用
Scope CSS 的本质是基于 HTML 和 CSS 属性选择器,即分别给 HTML 标签和 CSS 选择器添加 data-v-xxx标记
具体来说,它是通过 vue-loader 实现 的,实现过程大致分 3 步:
- 首先 vue-loader 会解析 .vue 组件,提取出 template、script、style 对应的代码块;
- 然后构造组件实例,在组件实例的选项上绑定 ScopedId;
- 最后对 style 的 CSS 代码进行编译转化,应用 ScopedId生成选择器的属性;
副作用:
父组件设置样式隔离,在父组件中的元素加上标识属性,子组件没有这个属性不生效父组件样式(但是会影响子组件根节点)
解决:
设置样式穿透,vue2中 有sass预处理器::v-deep,或者>>>; vue3中 :deep()
Vuex详解
这次彻底搞懂Vuex!
vue-router
Vue-Router的使用总结(含路由懒加载及守卫)
路由跳转
首先需要在页面结构设置一个路由组件的出口,即路由组件展示的位置
声明式导航
<router-link to="/home">home</router-link>
编程式导航
router.push('/home')
路由守卫分为全局守卫、路由独享守卫和组件内守卫
vue中key的作用
关于key的作用,官方是这样回答的:
key 的特殊 贡献主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
Vue很大的一个特点就是双向数据绑定,数据一旦改变,那么页面就渲染新的数据呈现在页面上。
对于用v-for渲染的列表数据来说,数据量可能一般很庞大,而且我们经常还要对这个数据进行一些增删改操作。假设我们给列表增加一条数据,整个列表都要重新渲染一遍,那不就很费事了。
而key的出现就是尽可能的回避这个问题,提高效率,如果我们给列表增加了一条数据,页面只渲染了这数据,那不就很完美了。
v-for默认使用就地复用策略,列表数据修改的时候,他会根据key值去判断某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的元素。
不推荐用index作为key。
index适用于在列表末尾增删元素,但是在列表中增删就会有较大开销。
虚拟DOM、diff算法
虚拟 DOM 简单说就是 用JS对象来模拟 DOM 结构
在组件中数据发生变化时,会触发 setter 然后通过 Notify 通知 Watcher,对应的 Watcher 会通知更新并执行更新函数,它会执行 render 函数获取新的虚拟 DOM,然后执行 patch 对比上次渲染结果的老的虚拟 DOM,并计算出最小的变化,然后再去根据这个最小的变化去更新真实的 DOM,也就是视图
key 的作用主要是为了更高效的更新虚拟 DOM,因为它可以非常精确的找到相同节点,因此 patch 过程会非常高效
Vue 在 patch 过程中会判断两个节点是不是相同节点时,key 是一个必要条件。比如渲染列表时,如果不写 key,Vue 在比较的时候,就可能会导致频繁更新元素,使整个 patch 过程比较低效,影响性能
应该避免使用数组下标作为 key,因为 key 值不是唯一的话可能会导致上面图中表示的 bug,使 Vue 无法区分它他,还有比如在使用相同标签元素过渡切换的时候,就会导致只替换其内部属性而不会触发过渡效果
从源码里可以知道,Vue 判断两个节点是否相同时主要判断两者的元素类型和 key 等,如果不设置 key,就可能永远认为这两个是相同节点,只能去做更新操作,就造成大量不必要的 DOM 更新操作,明显是不可取的
深入浅出虚拟 DOM 和 Diff 算法,及 Vue2 与 Vue3 中的区别
动态组件 keep-alive
动态组件与插槽
v-if v-show区别
vue中v-if和v-show的区别(源码分析)
在 v-if 的情况下,如果起始为false,只会生成空的注释节点用来占位,包含属性isComment: true和text: “”,在需要考虑白屏场景下,使用v-if比较合适。
当 v-show 点击切换成true时将会通过diff算法进行本地复用策略的优化,执行到v-show节点控制的节点渲染时节点key相同,采取原地复用的原则只对其属性display进行修改比从占位空注释节点变为真实节点更优,如果在transition这种频繁切换的场景中,进行v-show控制展示隐藏更合理。
v-if和v-show的使用需要根据场景,一般来说,v-if 有更高的切换开销,更多的使用在需要考虑白屏时间或者切换次数很少的场景;而 v-show 有更高的初始渲染开销但切换开销较小,因此,如果在transition控制的动画或者需要非常频繁地切换场景,则使用 v-show较好。
v-if 和 v-for为什么不能同时使用
v-for 和 v-if哪个优先级更高
在vue2中,v-for的优先级高于v-if
在vue3中,v-if的优先级高于v-for
两种混在一起写法均不被官方推荐
ref操作DOM
vue中的ref是把当前dom元素 “ 抽离出来 ” ,只要通过 this.$refs就可以获取到(注意this指向),此方法尤其适用于父元素需要操作子组件的dom元素,这也是子传父传递数据的一种方法
Vue3提升(重点要看)
Vue3的优势
- 编译阶段。对 diff 算法优化、静态提升等等
- 响应式系统。Proxy()替代Object.defineProperty()监听对象。监听一个对象,不需要再深度遍历,Proxy()就可以劫持整个对象
- 体积包减少。Compostion API 的写法,可以更好的进行 tree shaking,减少上下文没有引入的代码,减少打包后的文件体积 新增片段特性。
- Vue文件的template标签内,不再需要强制声明一个的div标签,节省额外的节点开销
vue和react对比
前端框架用vue还是react?清晰对比两者差异
前端工程化
模块化
模块化:将一个复杂的程序序依据一定的规则(规范)封装成几个块(文件),并进行组合在一起,内部数据与实现是私有的,只是向外部暴露一些接口(方法)与外部其它模块通信。
模块化开发中,通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数,并且可以按需加载。
依赖自动加载,按需加载。
提高代码复用率,方便进行代码的管理,使得代码管理更加清晰、规范。
减少了命名冲突,消除全局变量。
目前流行的js模块化规范有CommonJS、AMD、CMD以及ES6的模块系统
ES6 模块与 CommonJS 模块的差异
JS模块化——CommonJS AMD CMD UMD ES6 Module 比较
ES6模块化:ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
ES6的模块不是对象,import命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。
- CommonJS 模块输出的是一个值的浅拷贝,ES6 模块输出的是值的引用。
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。 - CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。
mixin
Mixins是一种能在不同组件里重用相同代码的方式。可以把多个组件共用的配置提取成一个混入对象。Mixin对象能够使用组件的任何属性(data,mounted,created,update等),而且当组件使用mixin时,这个mixin的所有信息也会混合到这个组件里,这个组件就能够访问到所有mixin的属性就像申明在自己对象中一样。
当 mixin 和组件本身包含重叠选项时,它们将使用适当的策略“合并”。
例如,数据对象进行递归合并,在发生冲突时组件的数据优先。
将同名的钩子函数合并到一个数组中,这样所有的钩子函数都会被调用。Mixin 钩子会在组件自己的钩子之前被调用。
单页面SPA
SPA(单页面应用,全程为:Single-page Web applications)指的是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序,简单通俗点就是在一个项目中只有一个html页面,它在第一次加载页面时,将唯一完成的html页面和所有其余页面组件一起下载下来,所有的组件的展示与切换都在这唯一的页面中完成,这样切换页面时,不会重新加载整个页面,而是通过路由来实现不同组件之间的切换。
单页面应用(SPA)的核心之一是:更新视图而不重新请求页面。
优点:
具有桌面应用的即时性、网站的可移植性和可访问性
用户体验好、快,内容的改变不需要重新加载整个页面
良好的前后端分离,分工更明确
缺点:
不利于搜索引擎的抓取
首次渲染速度相对较慢
vue-Rouer中的hash和history
hash和history路由的区别
前端路由的核心,就在于 —— 改变视图的同时不会向后端发出请求。
vue-router 在实现单页面路由时,提供了两种方式:Hash 模式和 History 模式;vue2是 根据 mode 参数来决定采用哪种方式,默认是 Hash 模式,手动设置为 History 模式。
hash模式是通过改变锚点(#)来更新页面URL,并不会触发页面重新加载,我们可以通过window.onhashchange监听到hash的改变,从而处理路由。
history模式是通过调用window.history对象上的一系列方法来实现页面的无刷新跳转。
vue-router 默认为 hash 模式,使用 URL 的 hash 来模拟一个完整的 URL,当 URL 改变时,页面不会重新加载;# 就是 hash符号,在 hash 符号后的值称为 hash 值。路由的 hash 模式是利用了 window 可以监听 onhashchange 事件来实现的,对服务器没有影响,HTTP 请求中也不会包括 hash 值。(同时每一次改变 hash 值,都会在浏览器的访问历史中增加一个记录,使用“后退”按钮,就可以回到上一个位置。所以,hash 模式 是根据 hash 值来发生改变,根据不同的值,渲染指定DOM位置的不同数据。)
hash特点
可以改变URL,但不会触发页面重新加载(hash的改变会记录在 window.hisotry 中)因此并不算是一次 HTTP请求,所以这种模式不利于 SEO 优化。只能修改 # 后面的部分,因此只能跳转与当前 URL 同文档的 URL。每改变一次 hash ( window.location.hash),都会在浏览器的访问历史中增加一个记录。路径中从 # 开始,后面的所有路径都叫做路由的 哈希值 并且哈希值它不会作为路径的一部分随着 http 请求,发给服务器。
history 是路由的另一种模式,在相应的 router 配置时将 mode 设置为 history 即可。history 模式是利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法来实现页面的无刷新跳转。
(这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会向后端发送请求。)
history特点
路径直接拼接在端口号后面,后面的路径也会随着http请求发给服务器,因此前端的URL必须和向发送请求后端URL保持一致,否则会报404错误。由于History API的缘故,低版本浏览器有兼容行问题。(新的URL可以是与当前URL同源的任意 URL,也可以与当前URL一样,但是这样会把重复的一次操作记录到栈中。)
开发环境中的history问题:
之前的一个vue项目改用history模式,部署线上后发现某个页面出现了404,经过一番研究,发现和nginx配置的资源路径有关系,但是使用hash模式不会有这种问题。 nginx 中配置按顺序检查参数中的资源是否存在,如果都没有找到,让 nginx 内部重定向到项目首页。
深入了解前端路由 hash 与 history 差异
计算机网络
计算机网络
计算机网络、数据结构
TCP 传输可靠性保障(传输层)
进程线程
进程、线程相关
OSI七层网络模型?(各层作用)
TCP/IP模型?
HTTP和HTTPS?
HTTP 1.0和HTTP 1.1的区别?
应用层常见协议?
为什么TCP三次握手?
为什么TCP四次挥手?
TCP和UDP区别?
TCP如何保证可靠传输?
从输入url到页面加载发生了什么?
状态码有哪些各自含义?
HTTP相关问题
关于面试:Http是什么?
Http (HTTP-Hypertext transfer protocol)是超文本传输协议, 是一个简单的请求-响应协议,HTTP基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII码形式给出;而消息内容则具有一个类似MIME的格式。
HTTP默认端口号为80,但是你也可以改为8080或者其他端口。
HTTP是无连接的:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。
HTTP是无状态的:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
HTTP状态码
HTTP 常用的状态码及使用场景
200: 请求成功
301:永久重定向
302:临时重定向
304:内容未改变
401:需要验证
403:拒绝访问
404:找不到资源(没有发现文件、查询或URl
500:服务器异常
对301、302状态码的理解
301 重定向
301 重定向的意思是 将网址永久的移动到新的 URL。
使用场景
网页或网站的链接更换新的主域名
某个网站中的网页已删除,但搜索引擎中还存在其内容
当你从 HTTP 切换到 HTTPS 时
永久合并两个或多个页面或网站时
二级域名跳转到主域名,集聚网站的权重
非法的黑帽 SEO(新网址和旧网址的内容并不是高度一致)
302 重定向
302 重定向的意思是将网址临时移动到新的 URL,302 重定向使用较少,但都是非常具体和个性化的情景:
使用特例
网站发生故障时,给出新的临时地址让用户访问
临时测试网站的新功能或网站页面时
A / B Test
网站需要长时间的维护和修改时
网站收录作弊时
你希望在不影响旧页面排名的情况下获得新页面的反馈时
当你正在进行促销,并希望暂时将访问者重定向到促销页面时
cookie和session作用区别?
URI和URL区别?
预检请求?成功状态码200
在前后端分离的项目中产生跨域请求时,对那些可能对服务器产生影响的HTTP请求,在每次调用接口时会先发送一个options请求,也叫预检请求。服务器同意跨域后,再发送真正的HTTP请求。
url输入到页面展示(深入
一道面试题是如何引发深层次的灵魂拷问?
五层网络协议:
1、应用层(DNS,HTTP):DNS解析成IP并发送http请求;
2、传输层(TCP,UDP):建立TCP连接(3次握手);
3、网络层(IP,ARP):IP寻址;
4、数据链路层(PPP):封装成帧;
5、物理层(利用物理介质传输比特流):物理传输(通过双绞线,电磁波等各种介质)。
DNS解析过程
- 客户端提出域名解析请求,并将请求发送到本地域名服务器。
- 本地域名服务器收到请求后,首先查询本地缓存。如果有这条记录,则本地域名服务器直接返回查询结果。
- 如果本地缓存中没有这条记录,本地域名服务器会直接向根域名服务器发送请求,然后根域名服务器会返回给本地域名服务器为主域名服务器查询域(根的子域)。地址。
- 本地服务器向上一步返回的域名服务器发送请求,然后接受请求的服务器查询自己的缓存。如果没有该记录,则返回相关下级域名服务器的地址。
- 重复步骤 4,直到找到正确的记录。
- 本地域名服务器将返回的结果保存到缓存中以备下次使用,并将结果返回给客户端。
get和post区别
GET 和 POST 有什么区别?
GET 请求和 POST 请求底层都是基于 TCP/IP 协议实现的,使用二者中的任意一个,都可以实现客户端和服务器端的双向交互。
GET 和 POST 最本质的区别是“约定和规范”上的区别,在规范中,定义 GET 请求是用来获取资源的,也就是进行查询操作的,而 POST 请求是用来传输实体对象的,因此会使用 POST 来进行添加、修改和删除等操作。 当然如果严格按照规范来说,删除操作应该使用 DELETE 请求才对,但在实际开发中,使用 POST 来进行删除的用法更常见一些。
按照约定来说,GET 和 POST 的参数传递也是不同的,GET 请求是将参数拼加到 URL 上进行参数传递的,而 POST 是将请参数写入到请求正文中传递的 。
- 缓存不同
GET 请求一般会被缓存,比如常见的 CSS、JS、HTML 请求等都会被缓存;而 POST 请求默认是不进行缓存的。 - 参数长度限制不同
GET 请求的参数是通过 URL 传递的,而 URL 的长度是有限制的,通常为 2k;而 POST 请求参数是存放在请求正文(request body)中的,所以没有大小限制。 - 回退和刷新不同
GET 请求可以直接进行回退和刷新,不会对用户和程序产生任何影响;而 POST 请求如果直接回滚和刷新将会把数据再次提交。 - 历史记录不同
GET 请求的参数会保存在历史记录中,而 POST 请求的参数不会保留到历史记录中。
预检请求options
options:与head类似,是客户端用于查看服务器的性能 。JavaScript的XMLHttpRequest对象进行CORS跨域资源共享时,就是使用OPTIONS方法发送预检请求,以判断是否有对指定资源的访问权限
预请求就是复杂请求(可能对服务器数据产生副作用的HTTP请求方法,如put,delete都会对服务器数据进行更修改,所以要先询问服务器)。
跨域请求中,浏览器自发的发起的预请求,浏览器会查询到两次请求,第一次的请求参数是options,以检测试实际请求是否可以被浏览器接受
顺序表、链表区别
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
Axios、Fetch
axios是基于Promise封装的ajax库,核心原理还是XMLHttpRequest。axios是基于promise的http库,可运行在浏览器端和node.js中。vue3.0放弃维护vue-resource,推荐使用axios库。
与Ajax对比:axios 是基于promise实现的对 ajax 技术的一种封装,两者用法基本一样,个别参数不同,axios 封装了一些更简便的 ajax 操作 axios 是 ajax,但是 ajax 不限于 axios。Ajax是发出异步HTTP请求的传统方式,调用XMLHttpRequest()方法。
具备特征:
- 从浏览器中创建 XMLHttpRequest
- 从 node.js 发出 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求和响应数据
- 取消请求
- 自动转换JSON数据
- 客户端支持防止 CSRF/XSRF
属性参数:
HTTP系列:AJAX基础梳理、axios基本使用梳理
Fetch 是在 ES6 出现的,它使用了 ES6 提出的 promise 对象。它是 XMLHttpRequest 的替代品。
特点:
使用 promise,不使用回调函数。
采用模块化设计,比如 rep、res 等对象分散开来,比较友好。
通过数据流对象处理数据,可以提高网站性能。
ajax、websocket
AJAX:局部刷新页面,无需重载整个页面。
简单来说,Ajax 是一种思想,XMLHttpRequest 只是实现 Ajax 的一种方式。
AJAX是一种无页面刷新的获取服务器资源的混合技术。而基于浏览器的“同源策略”,不同“域”之间不可以发送AJAX请求。但是在某些情境下,我们需要“跨域获取资源”,为了满足这一需求,我们可以使用“JSONP”与“CORS”两种技术。
轮询、SSE和webSocket
由于 http 存在一个明显的弊端(消息只能有客户端推送到服务器端,而服务器端不能主动推送到客户端),导致如果服务器如果有连续的变化,这时只能使用轮询,而轮询效率过低,并不适合。于是 WebSocket被发明出来。
WebSocket 是 Html5 定义的一个新协议,与传统的 http 协议不同,Websocket 是一个持久化的协议,该协议允许由服务器主动的向客户端推送信息。使用 WebSocket 协议的缺点是在服务器端的配置比较复杂。
WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
WebSocket 是一个全双工的协议,也就是通信双方是平等的,可以相互发送消息。
websocket特点:
- 无跨域问题。
- 支持双向通信,实时性更强;
- 可以发送文本,也可以二进制文件;
- 协议标识符是 ws,加密后是 wss ;
- 较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2~10字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的4字节的掩码。而HTTP协议每次通信都需要携带完整的头部;
- 支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)
再也不学AJAX了!(三)跨域获取资源 ③ - WebSocket & postMessage
前端登录token
Token其实就是访问资源的凭证。
一般是用户通过用户名和密码登录成功之后,服务器将登陆凭证做数字签名,加密之后得到的字符串作为token。
它在用户登录成功之后会返回给客户端,客户端主要有这么几种存储方式:
- 存储在localStorage 中,每次调用接口的时候都把它当成一个字段传给后台。
- 存储在cookie 中,让它自动发送,不过缺点就是不能跨域。
- 拿到之后存储在localStorage中,每次调用接口的时候放在HTTP请求头的Authorization字段里所以token在客户端一般存放于localStorage,cookie,或 sessionStorage中。
- 将token存放在webStroage中,可以通过周域的js来访问。这样会导致很容易受到xss攻击,特别是项目中引入很多第三方js类库的情况下。如果js脚本被盗用,攻击者就可以轻易访问你的网站, webStroage作为一种储存机制,在传输过程中不会执行任何安全标准。
Xss攻击: cross-site Scripting(跨站脚本攻击)是一种注入代码攻击。恶意攻击者在目标网站上注入script代码,当访问者浏览网站的时候通过执行注入的script代码达到窃取用户信息,盗用用户身份等。
- 将token存放在cookie中可以指定 httponly,来防止被Javascript读取,也可以指定secure,来保证token只在HTTPS下传输。缺点是不符合Restful最佳实践,容易受到CSRF攻击。
CSRF跨站点请求伪造(Cross-Site Request Forgery),跟XSS攻击一样,存在巨大的危害性。简单来说就是恶意攻击者盗用已经认证过的用户信息,以用户信息名义进行一些操作〈如发邮件、转账、购买商品等等)。由于身份已经认证过,所以目标网站会认为操作都是真正的用户操作的。CSRF并不能拿到用户信息,它只是盗用的用户凭证去进行操作。
前端安全,有哪些web攻击
1、XSS攻击,跨站脚本攻击(Cross Site Scripting),是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。恶意代码能够直接获取用户的信息,或者利用这些信息冒充用户向网站发起攻击者定义的请求。
分为存储型XSS、反射性XSS、DOM型XSS
存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。
DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。
措施:输入过滤:防止 HTML 中出现注入;防止 JavaScript 执行时,执行恶意代码。改成纯前端渲染,把代码和数据分隔开。对 HTML 做充分转义。避免 innerHTML、outerHTML 的 XSS 隐患。
2、CSRF攻击,分为get型攻击和post型攻击,攻击目标系统用户必须登录,这时才能拿到目标系统的cookie信息。CSRF通常是跨域的。
特点:
攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。
攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。
整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”。
跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。
措施:同源监测,CSRF Token,分布式校验、双重Token
3、SQL注入,通过把SQL命令插入到web表单提交或者输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
4、点击劫持,攻击者一般使用一个透明的、不可见的iframe,覆盖在一个网页上,然后诱使用户在不知情的情况下点击透明的iframe页面。通过调整iframe页面的位置,可以诱使用户恰好点击iframe页面的一些功能性按钮上。一般是通过禁止跨域的iframe来防范,禁止iframe的嵌套。
5、文件上传漏洞,用户通过利用windows文件命名规则,上传了可执行的脚本文件,并通过此脚本文件获得了执行服务端命令的能力。
前端安全系列(一):如何防止XSS攻击?
前端安全系列之二:如何防止CSRF攻击?
cookie、localStorage、sessionStorage
五步吃透前端缓存,让页面飞起
前端本地缓存方式
cookie:是比较老的前端缓存技术,它的特点是想要使用它前端必须要有服务(静态网页是不行的),而且存储大小限制在4kb。有涉及cookie的请求,cookie就要在服务器和浏览器之间来回传送,而且由于浏览器的跨域限制,客户端和服务端必须要保证同源。 cookie是存放在前端的,所以存在安全问题。
cookie参数:
cookie各个参数详解
cookie和storage的区别
1.cookie兼容所有的浏览器(本地cookie谷歌不支持),storage不支持IE6~8;
2.二者对存储的内容均有大小限制,前者同源情况写一般不能存储4kb的内容,后者同源一般能存储只能存储5MB的数据
3.cookie有过期时间,localStorage是永久存储(如果你不手动去删除的话)
4.一些浏览器处于安全的角度可能会禁用cookie,但无法禁用localStorage
sessionStorage和localStorage的区别
sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储。当用户关闭浏览器窗口后,数据立马会被删除。
localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。
JWT
傻傻分不清之 Cookie、Session、Token、JWT
JSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案。
是一种认证授权机制。
JWT 认证流程:
- 用户输入用户名/密码登录,服务端认证成功后,会返回给客户端一个 JWT
- 客户端将 token 保存到本地(通常使用 localstorage,也可以使用 cookie)
- 当用户希望访问一个受保护的路由或者资源的时候,需要请求头的 Authorization 字段中使用Bearer 模式添加 JWT,其内容看起来是下面这样
Authorization: Bearer <token>
服务端的保护路由将会检查请求头 Authorization 中的 JWT 信息,如果合法,则允许用户的行为
因为 JWT 是自包含的(内部包含了一些会话信息),因此减少了需要查询数据库的需要
因为 JWT 并不使用 Cookie 的,所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)
因为用户的状态不再存储在服务端的内存中,所以这是一种无状态的认证机制
其他问题
操作DOM方法
Javascript基础篇: 常见的Dom基本操作方法
- 文档元素选取,document.getElementById(id选择器)、getElementsByName(name选择器)、getElementsByTagName(标签选择器)、getElementsByClassName(类选择器)、querySelector、querySelectorAll
- DOM遍历,父节点-parentNode,子节点-childNodes,首子节点-firstChild,尾子节点-lastChild……
- 属性操作, 获取属性值 - getAttribute,属性值设置 - setAttribute,属性存在检测 - hasAttribute,删除属性 - removeAttribute,元素属性 - attributes
- 元素内容操作,元素内容 - innerHTML,元素及内容 - outerHTML, 纯文本元素内容 - textContent
- 创建节点,创建元素节点 - createElement,创建文本节点 - createTextNode,创建文档片段 - createDocumentFragment,创建注释节点 – createCmoment,节点克隆 – cloneNode(传参数true表示深克隆,false表示浅复制)
- 插入节点,插入子节点 – appendChild, 节点前插入 – insertBefore
- 删除和替换,删除子节点 – removeChild,替换子节点 – replaceChild
js放到head之间和body之间的区别
在HTML body部分中的JavaScripts会在页面加载的时候被执行。
在HTML head部分中的JavaScripts会在被调用的时候才执行。
页面的渲染是从上向下渲染。如果某段js代码中涉及页面某个元素,把这段js 直接放到head 那在页面渲染的时候就会报undefind 错误。
放在head中的JS代码会在页面加载完成之前就读取,而放在body中的JS代码,会在页面加载完成之后读取。
DOMContentLoaded和load的区别
DOMContentLoaded 事件在 html文档加载及解析完毕,并且 html 所引用的内联 js、以及外链 js 的同步代码都执行完毕后触发。(因为js加载和解析会阻塞DOM的解析)
当页面 DOM 结构中的 js、css、图片,以及 js 异步加载的 js、css 、图片都加载完成之后才会触发 load 事件。
1.DOMContentLoaded是HTML文档(CSS、JS)被加载以及解析完成之后触发(即 HTML->DOM的过程完成 );
2.load需要在页面的图片、视频等加载完后被触发,而DOMContentLoaded不需要等待这些资源加载完成;
判断js数据类型方法
JS判断数据类型方法的总结
数组方法
JS数组方法总览及遍历方法耗时统计
let const暂存性死区
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
let同样存在变量提升(hoisting),只是形式与var不同,var定义的变量将会被赋予undefined的初始值,而let在被显式赋值之前不会被赋予初始值,并且在赋值之前读写变量都会导致 ReferenceError 的报错。从代码块(block)起始到变量求值(包括赋值)以前的这块区域,称为该变量的暂时性死区。
ECMAScript 6 – 变量的暂时性死区
变量提升
JavaScript引擎会在编译阶段进行数项优化,有些优化通过对代码静态分析,并预先确定所有变量和函数的定义位置,并用合适的作用域将它们关联起来,才能在执行过程中快速找到标识符。
因此,正确的思路是:包含变量和函数在内的所有声明都会在任何代码执行前首先被处理。
== 和 === 的区别
JavaScript 提供了严格( === , ! == ) 和类型转换(==, !=) 相等比较。严格运算符考虑变量的类型,而非严格运算符根据变量的值进行类型校正/转换。严格的运算符遵循以下不同类型的条件,
- 当两个字符串具有相同的字符序列、相同的长度和对应位置的相同字符时,它们是严格相等的。
- 当两个数字在数值上相等时,它们是严格相等的。即,具有相同的数值。这里面有两种特殊情况,
- NaN 不等于任何东西,包括 NaN。
- 正零和负零彼此相等。
- 如果两个布尔操作数都为真或都为假,则两个布尔操作数严格相等。
- 如果两个对象引用同一个对象,则它们是严格相等的。
- Null 和 Undefined 类型不等于 === ,但等于 == 。即,null === undefined --> false 但 null == undefined --> true
类型转换
JS 隐式类型转换
隐式转换就是自动转换,通常发生在一些数学运算中。因为 JavaScript 是一种弱类型的语言,在一个表达式中,运算符两边的类型可以不同(比如一个字符串和一个数字相加),JavaScript 解释器会在运算之前将它们的类型进行转换。
通过运行结果可以得出:
字符串加数字,数字会转换为字符串;
数字减字符串,字符串会转换为数字,如果字符串无法转换为数字(例如"abc"、“JavaScript”),则会转换为 NaN;
字符串减数字,字符串会转换为数字,如果字符串无法转换为数字,则会转换为 NaN;
乘、除运算时,也会先将字符串转换为数字。
JS 强制类型转换
与隐式类型转换相反,强制类型转换需要手动进行,在 JavaScript 中,强制类型转换主要是通过调用全局函数来实现的,例如 Number()、Boolean()、parseInt()、parseFloat() 等。
document.write(Number("10.5")); // 输出:10.5
document.write(Number(true)); // 输出:1
document.write(Number(false)); // 输出:0
document.write(Number(null)); // 输出:0
null 和 undefined在区别
null: 表示一个值被定义了,定义为空值
undefined: 表示根本不存在定义
所以设置一个值为 null 是合理的,
如: obj.value = null,
但是设置成一个undefined 的值是不合理的,
比如 obj.value = undefined , 因为 undefined表示这个值根本不存在,不能直接赋值undefined
commonjs 和 ESM的区别
commonjs 和 esm 的主要区别可以概括成以下几点:
- 输出拷贝 vs 输出引用
- esm 的 import read-only 特性
- esm 存在 export/import 提升
页面隐藏元素方法
- display:none,可以将元素在页面中彻底消失,会导致浏览器的重排的重绘。(元素不可见,不占据空间,无法响应点击事件)
- visibility:hidden从页面上来看隐藏该元素,DOM依然会存在,只是处于一个不可见的状态。不会发生重排,但是会发生重绘。(元素不可见,占据空间,无法响应点击事件)
- opacity:0,表示元素的透明度设置为0之后,元素也是可以达到隐藏效果的,不会引发重排,一般情况下也会引发重绘。它是存在于页面之上的,所以他自身的事件仍然是可以触发的,但被他遮挡的元素是不能触发其他事件的。(改变元素透明度,元素不可见,占据页面空间,可以响应点点击事件。)
- 将元素的margin、border、padding、height和width等影响元素盒模型的属性设置成0,如果元素内有子元素的内容,还要设置其overflow:hidden 来隐藏子元素(元素不可见,不占据空间,无法响应点击事件)
- position:absolute,使用top,left将元素移出页面(元素不可见,不影响布局)
数据劫持重写数组方法
push(),pop(),shift(),unshift(),splice(),sort(),reverse()
源码解析:
Vue2.x 原理系列-数组劫持
数组是一种类列表对象,它的原型中提供了遍历和修改元素的相关操作。JavaScript数组的长度和元素类型都是非固定的。因为数组的长度可随时改变,并且其数据在内存中也可以不连续,所以 JavaScript 数组不一定是密集型的,这取决于它的使用方式。
函数柯里化
柯里化:是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回 接受余下的参数且返回结果的新函数 的技术。
「前端面试题系列6」理解函数的柯里化
JS是如何运行的
JavaScript代码是如何被执行的
堆和栈
栈是一种特殊的列表,栈内的元素只能通过列表的一端访问,这一端称为栈顶。 栈被称为是一种后入先出(LIFO,last-in-first-out)的数据结构。
栈内存自动分配相对固定大小的内存空间,并由系统自动释放。
堆是一种经过排序的树形数据结构,每个结点都有一个值。 通常我们所说的堆的数据结构,是指二叉堆。
引用数据类型占据空间大、大小不固定。 如果存储在栈中,将会影响程序运行的性能; 引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。 当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体
堆内存是动态分配内存,内存大小不一,也不会自动释放。
栈内存和堆内存的垃圾回收
栈内存中变量一般在它的当前执行环境结束就会被销毁被垃圾回收制回收, 而堆内存中的变量则不会,因为不确定其他的地方是不是还有一些对它的引用。 堆内存中的变量只有在所有对它的引用都结束的时候才会被回收。
闭包中的基本数据类型变量不保存在栈内存中,而是保存在堆内存中。
闭包中引用的变量并不保存中栈内存中,而是保存在堆内存中。 这也就解释了函数调用之后之后为什么闭包还能引用到函数内的变量。
mutation
DOM 规范 —— MutationObserver 接口
MutationObserver 是现代浏览器提供的用来检测 DOM 变化的网页接口。你可以使用这个接口来监听新增或者删除节点,属性更改,或者文本节点的内容更改。
MutationObserver 可以观察整个 文档、DOM 树的一部分 或 具体 dom 元素,主要是观察元素的 属性、子节点、文本 的变化,并且可以在 DOM 被修改时异步执行回调。
js对dom的操作是在
当前任务队列里的宏任务微任务都执行结束后才执行的。js代码执行是由js引擎线程负责的,dom样式更改是由GUI渲染线程负责的,所以两个线程是互斥的,造成了dom操作“异步”的效果,而且GUI渲染会进行优化,多个同一dom的操作会合并。
所以dom更改异步,就照成了在更改dom后执行回调函数也异步了,vue源码$nextTick也就使用了这一特性,配合MutationObserver
API很好实现了微任务效果。
响应式布局
自适应布局是通过检测视口的宽度比例,不同的宽度比例请求不同的页面。
响应式布局是通过检测视口的宽度,通过@media等方式,来做页面的响应式布局。
响应式布局:代码冗余不好维护。
自适应布局:多套代码,易于维护。
响应式:只需要开发一套代码。 响应式设计通过检测视口分辨率,针对不同客户端在客户端做代码处理,来展现不同的布局和内容。
自适应:需要开发多套界面。 通过检测视口分辨率,来判断当前访问的设备是:pc端、平板、手机,从而请求服务层,返回不同的页面。
实现响应式:
媒体查询,百分比,vw/vh,rem,UI组件框架
响应式布局方法总结
单例模式
单例模式怎么实现?
核心:确保只有一个实例,并提供全局访问(注意:全局变量不是单例模式!)
在单例模式的实现过程中,需要注意:
1、单例类的构造函数为公有
2、提供一个本身的动态公有成员变量
3、提供一个私有的动态工厂办法
二、实现思路
一般情况下,当我们创建了一个类,本质是构造函数,可以通过new关键字调用构造函数进而生成任意多的实例对象。
但:单例模式要实现的是,不管我们尝试去创建多少次,都只返回第一次所创建的那唯一一个实例
要实现这一点就需要构造函数,具备判断自己是否已经创建过一个实例的能力
单例模式作为泛滥设计模式中最简略的一个设计模式,为了保障某个类只存在一个实例,这个类称为单例类,它提供全局访问的办法。在单例类的外部实现只生成一个实例,同时它提供一个动态的工厂办法,让客户能够应用它的唯一实例;为了避免在内部对其实例化,将其构造函数设计为公有。
代码调试
JS 的 6 种打断点的方式,你用过几种?
打包部署
前端Vue项目打包部署实战教程
流程:npm run build打包项目代码生成dist文件夹,服务器安装并启动nginx反向代理,配置服务器路径端口号等,部署打包后的代码。(history模式刷新404问题)
原因:单页面应用,history模式下路径直接拼接在端口号后面,后面的路径也会随着http请求发给服务器,因此前端的URL必须和向发送请求后端URL保持一致,刷新会发送服务器请求,服务器没有配置路由的url地址会出现404错误
解决方法:nginx代理,在 nginx 中配置按顺序检查参数中的资源是否存在,如果都没有找到,重定向到主页,再通过前端路由到对应页面。
nginx配置+history模式问题
Vue 项目打包部署总结
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强。
反向代理“代理”的是服务器端,而且这一个过程对于客户端而言是透明的。
组件设计
面试官(6): 写过『通用前端组件』吗?
编写高质量可维护的代码:组件的抽象与粒度
未来趋势/前沿技术
2022年哪些前端技术点会火?
2022,前端的天🌦️要怎么变?
一文带你进入微前端世界
2022年你必须要会的微前端 -(实战篇)
职业规划
如果有一天不做前端了,我会做什么?
职业发展和未来的规划
一名前端实习生在京东的成长小结
项目中沟通
谈谈前端人员在项目中的正确沟通方式
值得前端工程师学习的团队沟通话术
知识图谱
30多个指标全面比较Neo4j、HugeGraph、JanusGraph
7种图数据库简单比较