一.热门js面试
1.简述同步和异步的区别?
同步:
浏览器访问服务器请求,用户看到页面刷新 ,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作
异步:
浏览器访问服务器请求,用户正常操作,浏览器后端进行请求,等请求完,页面不刷新,新内容也会出现,用户看到新内容
2.怎么添加,移除,复制,创建和查找节点
1) 创建节点
createDocumentFragment() // 创建一个DOM片段
createElement() // 创建一个具体的元素
createTextNode() // 创建一个文本节点
2) 添加,移除,替换,插入
appendChild()
removeChild()
replaceChild()
insertBefore()
3) 查找
getElementByTagName() // 通过标签名称
getElementByName() // 通过元素的Name属性的值
getElementById() // 通过元素ID,唯一性
3.实现一个函数clone可以对JavaScript深度克隆
原型链:
Object.prototype.clone = function() {var o = this.constructor === Array ? [] : {}for(var e in this) {o[e] = (typeof this[e] === 'object' && this[e] != null) ? this[e].clone() : this[e];}return o; }var o1={a:1,b:2,c:{aa:11,bb:22}} var o2=o1.clone() o2.c.aa=33 console.log(o1,o2)
或者function递归:
function clone (obj) {var o1if(typeof obj === 'object' && obj != null) {o1 = Array.isArray(Array) ? [] : {}} else {o1 = obj}for (let key in obj) {o1[key] = (typeof obj[key] === 'object' && obj != null) ? clone(obj[key]) : obj[key]}return o1}var o1={a:1,b:2,c:{aa:11,bb:22}}var o2=clone(o1)o2.c.aa=33;console.log(o1,o2);
确定对象类型的方法:
constructor
属性返回创建实例对象的构造函数。- 通过检查
constructor
是否为Array
,代码试图判断对象是否为数组类型。这是一种比较直观的方式来区分对象和数组。- 例如,对于一个数组
let arr = [1, 2, 3];
,arr.constructor
返回Array
;- 对于一个普通对象
let obj = { key: 'value' };
,obj.constructor
返回Object
。类型判断方法对比:
typeof
局限性:虽然typeof
可以区分基本数据类型(如number
、string
、boolean
等),但对于对象和数组,typeof
都返回object
。例如,typeof []
和typeof {}
都返回object
,所以仅用typeof
无法区分数组和普通对象。instanceof
适用性:instanceof
可以用来检查一个对象是否是某个构造函数的实例。但是,instanceof
在跨iframe
或者复杂的继承场景下可能会出现问题,并且在判断基本数据类型的包装对象(如new Number(1)
)时可能不符合预期。而这里使用constructor
相对简单直接,只要对象的constructor
属性没有被篡改,就可以比较准确地判断是否为数组。- Object.prototype.toString.call:使用场景更广,比如可以判断数据类型是不是Promise
var o1 = Promise.resolve(1);console.log(Object.prototype.toString.call(o1)) // VM12232:1 [object Promise]
潜在风险和注意事项
constructor
属性可能被修改:对象的constructor
属性是可以被修改的。例如,let arr = [1, 2, 3]; arr.constructor = Object;
这样的操作会导致代码中的类型判断出错。所以这种判断方式在一些复杂的、不可控的代码环境中可能存在风险。- 更安全的替代方法:
- 更安全的判断数组的方法可以是使用
Array.isArray()
函数。例如,var o1 = Array.isArray(obj)? [] : {};
这样的写法会更加可靠,因为Array.isArray()
不会受到constructor
属性被修改的影响。- 使用Object.prototype.toString.call
function clone (obj) {var o1if(typeof obj === 'object' && obj != null) {o1 = Array.isArray(Array) ? [] : {}} else {o1 = obj}for (let key in obj) {o1[key] = (typeof obj[key] === 'object' && obj != null) ? clone(obj[key]) : obj[key]}return o1}var o1={a:1,b:2,c:{aa:11,bb:22}}var o2=clone(o1)o2.c.aa=33;console.log(o1,o2);
4.如何消除一个数组中重复的元素
1) arr.indexOf(item) === -1时往arr数组push元素item:
function qc (arr1) {let arr = []for (let i = 0;i < arr1.length; i++) {if (arr.indexOf(arr1[i]) === -1) {arr.push(arr1[i])}}return arr }var arr1 = ["1","1","3","5","2","24","4","4","a","a","b"];console.log(qc(arr1));
2) Set
var arr1 = ["1","1","3","5","2","24","4","4","a","a","b"];var arr = [...(new Set(arr1))]console.log(arr)
5.写一个返回闭包的函数
闭包是JavaScript的一个难点,也是它的特色,很多高级应用都要依靠闭包.闭包就是能够读取其他函数内部变量的函数.可以把闭包简单理解为定义在一个函数内部的函数
闭包的三个特性:
- 函数嵌套函数
- 函数内部可以引用函数外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
闭包就是一个函数的返回值为另一个函数,在outer外部可以通过这个返回的函数访问outer内部的局部变量
function outer() {var val=0;return function() {val+=1document.write(val + "<br />");} }var outObj=outer() outObj() outObj() outObj=null//val被回收var outObj1=outer() outObj1() outObj1() outObj1=null//val被回收
- 闭包会使变量始终保存在内存中,如果不当使用会增大内存消耗
- 如果上例中定义很多outer(),则内存中会保存很多val变量
JavaScript的垃圾回收机制:
- 在JavaScript中,如果一个对象不再被引用,那么这个对象就会被GC回收
- 如果两个对象互相引用,而不再被第三者所引用,那么这两个互相引用的对象也会被回收
使用闭包有什么好处:
- 希望一个变量长期驻扎在内存中
- 避免全局变量的污染
- 私有成员的存在
6.使用递归完成1到100的累加
1) 递归累加
function sum(num) {if(num === 1) return 1return num + sum(num - 1) } console.log(sum(100))
2) 转换为数组+reduce相加
// 创建一个从1到100的数组 const numbers = Array.from({ length: 100 }, (_, index) => index + 1);// 使用 reduce 方法将数组中的数字相加 const sum = numbers.reduce((acc, cur) => acc+ cur, 0); console.log(sum); // 输出: 5050
7.JavaScript有哪几种数据类型
基本数据类型:
字符串 String
数字 Number
布尔 Boolean
复合数据类型:
数组 Array
对象 Object
特殊数据类型:
空对象 Null
未定义 Undefined
引用类型:
数组,对象,function
8.如何判断数据类型
判断js中的数据类型的几种方法:
typeof、instanceof、 constructor、 prototype、 $.type()/jquery.type(),
1) 最常见的判断方法:typeof
使用场景:
typeof 可以判断function的类型;在判断除Object类型的对象时比较方便。
2) 判断已知对象类型的方法: instanceof
alert(c instanceof Array) ---------------> true alert(d instanceof Date) alert(f instanceof Function) ------------> true alert(f instanceof function) ------------> false
使用场景:
注意:instanceof 后面一定要是对象类型,并且大小写不能错,该方法适合一些条件选择或分支。
3) 根据对象的constructor判断: constructor
alert(c.constructor === Array) ----------> true alert(d.constructor === Date) -----------> true alert(e.constructor === Function) -------> true 注意: constructor 在类继承时会出错 eg:function A(){};function B(){};A.prototype = new B(); //A继承自Bvar aObj = new A();alert(aobj.constructor === B) -----------> true;alert(aobj.constructor === A) -----------> false; 而instanceof方法不会出现该问题,对象直接继承和间接继承的都会报true:alert(aobj instanceof B) ----------------> true;alert(aobj instanceof B) ----------------> true; 言归正传,解决construtor的问题通常是让对象的constructor手动指向自己:aobj.constructor = A; //将自己的类赋值给对象的constructor属性alert(aobj.constructor === A) -----------> true;alert(aobj.constructor === B) -----------> false; //基类不会报true了;
注意:
- constructor在类继承时会出错
- 解决constructor的问题是让对象的constructor手动指向自己
比如使用Object.create继承后再加一句functionName.prototype.constructor=functionName:
function Animal() { } function Cat() {Animal.call(this) } Cat.prototype = Object.create(Animal.prototype) var cat = new Cat()console.log(cat.constructor) // Animal console.log(cat instanceof Cat) // true console.log(cat instanceof Animal) // true// 期望的是cat.constructor应该是Cat类,所以重新让prototype对象的constructor指向自己 Cat.prototype.constructor=Cat console.log(cat.constructor) // Cat console.log(cat instanceof Cat) console.log(cat instanceof Animal) // true
9.console.log(1+'2')和console.log(1-'2')的打印结果
console.log(1+'2') //12 console.log(1-'2') //-1
10.js事件委托是什么,原理是什么?
事件委托:
利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件
事件冒泡:
就是事件从最深的节点开始,然后逐步向上传播事件。
11.如何改变函数内部的this指针的指向
1) bind
fun.bind(thisArg, arg1, arg2, ...)
直接改变这个函数的this指向并且返回一个新的函数,之后再次调用这个函数的时候this都是指向bind绑定的第一个参数,bind传参方式根call方法一致
thisArg当绑定函数被调用时,该函数会作为原函数运行时的this指向.当使用new操作符调用绑定函数时,该参数无效
arg1,arg2,...,当绑定函数被调用时,这些餐宿将置于实参之前传递给绑定的方法
2) call
- fun.call(thisArg, arg1, arg2, ...)
- call和apply的用法几乎一样,唯一的不同是传递的参数不同,call只能一个参数一个参数传入
- thisArg:在fun函数运行时指定的this值,需要注意的是,指定的this值并不一定是该函数执行时真正的this值
- 如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象
- arg1,arg2,...指定的参数列表
3) apply
- fun.apply(thisArg, []arg1, arg2, ...])
- apply则只支持传入一个数组,哪怕一个参数也是数组形式.最终调用函数的时候这个数组会拆成一个个参数分别传入,需要注意的是,指定的this值并不一定是该函数执行时真正的this值
- 如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this值会指向该原始值的自动包装对象
- [arg1,arg2,...]指定的参数列表
4) 总结
- 当我们使用一个函数需要改变this指向的时候才会用到call,apply,bind
- 如果你要传递的参数不多,则可以使用fn.call(thisArg,arg1,arg2,...)
- 如果你要传递的参数很多,则可以使用数组将参数整理好调用fn.apply(thisArg,[arg1,arg2,...])
- 如果你想生成一个新函数长期绑定某个函数给某个对象使用,则可以使用const f1=fn.bind(thisArg);f1(arg1,arg2,...);
- call和apply第一个参数为null/undefined,函数this指向全局对象,在浏览器中是window,在node中是global
12.跨域的几种方式?
jsonp
script标签是不受同源策略影响的,它可以引入来自任何地方的js文件。动态添加script
使用window.name来进行跨域
window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
使用HTML5中新引进的window.postMessage方法来跨域传送数据
window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。
反向代理
websocket
cors
13.谈谈垃圾回收机制的方式及内存管理
一、垃圾回收机制—GC
Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。
原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存
通常情况下有两种实现方式:标记清除和引用计数
- 标记清除: js中最常用的垃圾回收方式就是标记清除。
当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。
- 引用计数的含义是跟踪记录每个值被引用的次数。
当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。
二、内存管理
1)、Javascript引擎基础GC方案是(simple GC):mark and sweep(标记清除),即:
- (1)遍历所有可访问的对象。
- (2)回收已不可访问的对象。
2)、GC的缺陷
和其他语言一样,javascript的GC策略也无法避免一个问题:GC时,停止响应其他操作,这是为了安全考虑。而Javascript的GC在100ms甚至以上,对一般的应用还好,但对于JS游戏,动画对连贯性要求比较高的应用,就麻烦了。这就是新引擎需要优化的点:避免GC造成的长时间停止响应。
3)、GC优化策略
- 1)分代回收(Generation GC)
这个和Java回收策略思想是一致的。目的是通过区分“临时”与“持久”对象;多回收“临时对象”区(young generation),少回收“持久对象”区(tenured generation),减少每次需遍历的对象,从而减少每次GC的耗时- 2)增量GC
这个方案的思想很简单,就是“每次处理一点,下次再处理一点,如此类推”
14.清除字符串左右两端空格
//重写trim方法
if(!String.prototype.trim){String.prototype.trim = function(){return this.replace(/^\s+/,"").replace(/\s+$/,""); }
}
//写fntrim去掉左右空格
function fntrim(str){return str.replace(/^\s+/,"").replace(/\s+$/,"");
}
15.js继承
父类,代码如下:
// 定义一个动物类 function Animal (name) {// 属性this.name = name || 'Animal';// 实例方法this.sleep = function(){console.log(this.name + '正在睡觉!');} } // 原型方法 Animal.prototype.eat = function(food) {console.log(this.name + '正在吃:' + food); };
1) 原型链继承
核心:将父类的实例作为子类的原型
function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat';// Test Code var cat = new Cat(); console.log(cat instanceof Animal); //true console.log(cat instanceof Cat); //true
核心:将父类的实例作为子类的原型
特点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法/原型属性,子类都能访问到
- 简单,易于实现
缺点:
- 要想为子类新增属性和方法,必须要在
new Animal()
这样的语句之后执行,不能放到构造器中- 无法实现多继承
- 来自原型对象的所有属性被所有实例共享(来自原型对象的引用属性是所有实例共享的)(详细请看附录代码:[示例1](javascript:void(0);))
- 创建子类实例时,无法向父类构造函数传参
2) 构造继承
**核心:**使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Cat(name){Animal.call(this);this.name = name || 'Tom'; }// Test Code var cat = new Cat(); console.log(cat instanceof Animal); // false console.log(cat instanceof Cat); // true
特点:
- 解决了1中,子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(call多个父类对象)
缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
3) 实例继承
**核心:**为父类实例添加新特性,作为子类实例返回
function Cat(name){var instance = new Animal();instance.name = name || 'Tom';return instance; }// Test Code var cat = new Cat(); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // false
特点:
- 不限制调用方式,不管是
new 子类()
还是子类()
,返回的对象具有相同的效果缺点:
实例是父类的实例,不是子类的实例
不支持多继承
4) 拷贝继承
function Cat(name){var animal = new Animal();for(var p in animal){Cat.prototype[p] = animal[p];}Cat.prototype.name = name || 'Tom'; }// Test Code var cat = new Cat(); console.log(cat instanceof Animal); // false console.log(cat instanceof Cat); // true
特点:
- 支持多继承
缺点:
- 效率较低,内存占用高(因为要拷贝父类的属性)
- 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
5) 组合继承
**核心:**通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name){Animal.call(this);this.name = name || 'Tom'; } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat; // Test Code var cat = new Cat(); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // true
特点:
- 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可复用
缺点:
- 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
6) 寄生组合继承
**核心:**通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
function Cat(name){Animal.call(this);this.name = name || 'Tom'; } (function(){// 创建一个没有实例方法的类var Super = function(){};Super.prototype = Animal.prototype;//将实例作为子类的原型Cat.prototype = new Super(); })();// Test Code var cat = new Cat(); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); //true Cat.prototype.constructor = Cat; // 需要修复下构造函数
特点:
- 堪称完美
缺点:
- 实现较为复杂
16.判断一个变量是否是数组
1) isArray
Array.isArray([1,2,3]) //此处返回true2、但是某些比较老的浏览器,比如IE8及以下,没有实现Array的isArray方法,那么就需要换一种方式来判断:
Object.prototype.toString.call([1,2,3]) //返回字符串:'[object Array]'
function isArray (value) {if (Object.prototype.toString.call(value) === '[object Array]') {return true}return false}
17.let,const,var有什么区别?
- let 和 const 定义的变量不会出现变量提升,而 var 定义的变量会提升。
- let 和 const 是JS中的块级作用域
- let 和 const 不允许重复声明(会抛出错误)
- let 和 const 定义的变量在定义语句之前,如果使用会抛出错误(形成了暂时性死区),而 var 不会。
- const 声明一个只读的常量。一旦声明,常量的值就不能改变(如果声明是一个对象,那么不能改变的是对象的引用地址)
18.监听函数和普通函数有什么区别?
- 箭头函数是匿名函数,不能作为构造函数,不能使用new
- 箭头函数不绑定arguments,取而代之的是rest函数...解决
- 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
- 监听函数通过call或者apply方法调用一个函数时,只传入一个参数,对this没有影响
- 箭头函数通过call或apply调用一个函数时,只传入了一个参数,对this并没有影响
- 监听函数没有原型属性
- 箭头函数不能当做Generator函数,不能使用yield关键字
19.随机取1-10的整数
Math.floor(Math.random()*10+1)
20.new具体做了什么?
- 创建一个空对象,并且
this
变量引用该对象,同时还继承了该函数的原型- 属性和方法被加入到
this
引用的对象中- 新创建的对象由
this
所引用,并且最后隐式的返回this
21.Ajax原理
- ajax的原理就是在用户和服务器之间加了一个中间层(ajax引擎),通过XmlHttpRequest对象来向服务器发异步请求,从服务器获取数据,然后使用JavaScript来操作DOM而更新页面.使用户操作与服务器响应异步化.这其中最关键的一步就是从服务器获得请求数据
- ajax的过程只涉及JavaScript,XMLHttpRequest和DOM,XMLHttpRequest是ajax的核心机制
var xhr = null xhr = new XMLHttpRequest() // 连接服务器 xhr.open('get', null, true) // 发送请求 xhr.send(null) // 接受请求 xhr.onreadystatechange = function() {if(xhr.readyState == 4) {if(xhr.status==200) {success(xhr.responseText)} else {fail && fail(xhr.status)}} }
22模块化开发怎么做?
比如立即执行函数(或闭包函数),不暴露私有成员
var module1 = (function(){var _count = 0;var m1 = function(){//...};var m2 = function(){//...};return {m1 : m1,m2 : m2};})();
23.异步加载js的方式有哪些?
- defer,只支持
IE
async
:- 创建
script
,插入到DOM
中,加载完毕后callBack
24.xml和json的区别?
数据体积方面
JSON
相对于XML
来讲,数据的体积小,传递的速度更快些。数据交互方面
JSON
与JavaScript
的交互更加方便,更容易解析处理,更好的数据交互数据描述方面
JSON
对数据的描述性比XML
较差传输速度方面
JSON
的速度要远远快于XML
25.webpack如何实现打包的?
WebPack
是一个模块打包工具,你可以使用WebPack
管理你的模块依赖,并编绎输出模块们所需的静态文件。- 它能够很好地管理、打包
Web
开发中所用到的HTML
、Javascript
、CSS
以及各种静态文件(图片、字体等),让开发过程更加高效。对于不同类型的资源,webpack
有对应的模块加载器。webpack
模块打包器会分析模块间的依赖关系,最后 生成了优化且合并后的静态资源
26.常见web安全及防护原理
sql
注入原理
- 就是通过把
SQL
命令插入到Web
表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令总的来说有以下几点
- 永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双
"-"
进行转换等- 永远不要使用动态拼装SQL,可以使用参数化的
SQL
或者直接使用存储过程进行数据查询存取- 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接
- 不要把机密信息明文存放,请加密或者
hash
掉密码和敏感的信息XSS原理及防范
Xss(cross-site scripting)
攻击指的是攻击者往Web
页面里插入恶意html
标签或者javascript
代码。比如:攻击者在论坛中放一个看似安全的链接,骗取用户点击后,窃取cookie
中的用户私密信息;或者攻击者在论坛中加一个恶意表单,当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点XSS防范方法
- 首先代码里对用户输入的地方和变量都需要仔细检查长度和对
”<”,”>”,”;”,”’”
等字符做过滤;其次任何内容写到页面之前都必须加以encode,避免不小心把html tag
弄出来。这一个层面做好,至少可以堵住超过一半的XSS 攻击XSS与CSRF有什么区别吗?
XSS
是获取信息,不需要提前知道其他用户页面的代码和数据包。CSRF
是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。要完成一次CSRF
攻击,受害者必须依次完成两个步骤登录受信任网站
A
,并在本地生成Cookie
在不登出
A
的情况下,访问危险网站B
CSRF的防御
- 服务端的
CSRF
方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数- 通过验证码的方法
27.用过哪些设计模式?
工厂模式:
- 工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法
- 主要好处就是可以消除对象间的耦合,通过使用工程方法而不是
new
关键字构造函数模式:
- 使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,该模式与工厂模式的不同之处在于
- 直接将属性和方法赋值给
this
对象;
28.为什么同源限制?
- 同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议
- 举例说明:比如一个黑客程序,他利用
Iframe
把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript
读取到你的表单中input
中的内容,这样用户名,密码就轻松到手了。
29.offsetWidth/offsetHeight,clientWidth/clientHeight与scrollWidth/scrollHeight的区别
- offsetWidth/offsetHeight返回值包括content+padding+border,效果和getBoundingClientRect()相同
- clientWidth/clientHeight返回值只包含content+padding+border,如果有滚动条,也不包含滚动条
- scrollWidth/scrollHeight返回值包含content+padding+溢出内容的尺寸
30.JavaScript有哪些方法定义对象
- 对象字面量:
var obj = {};
- 构造函数:
var obj = new Object();
- Object.create():
var obj = Object.create(Object.prototype);
31.对Promise的了解?
Promise有四种状态:
- pending:初始状态
- fullfilled:成功的操作
- rejected:失败的操作
- settled: promise已经fullfilled或者rejected,且不是pending
注意:fullfilled和rejected合成settled
Promise对象常用来用延迟和异步计算
Promise的构造函数:
var promise = new Promise(function(resolve, reject) {if (...) { // succeedresolve(result);} else { // failsreject(Error(errMessage));}});
Promise
实例拥有then
方法(具有then
方法的对象,通常被称为thenable
)。它的使用方法如下:1promise.then(onFulfilled, onRejected)
接收两个函数作为参数,一个在
fulfilled
的时候被调用,一个在rejected
的时候被调用,接收参数就是future
,onFulfilled
对应resolve
,onRejected
对应reject
32.谈谈对AMD,CMD的理解?
CommonJS
是服务器端模块的规范,Node.js
采用了这个规范。CommonJS
规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD
规范则是非同步加载模块,允许指定回调函数
AMD
推荐的风格通过返回一个对象做为模块对象,CommonJS
的风格通过对module.exports
或exports
的属性赋值来达到暴露模块对象的目的
33.web开发中会话跟踪的方法有哪些?
cookie
session
url
重写- 隐藏
input
ip
地址
34.介绍js有哪些内置对象?
- Object是JavaScript中所有对象的父对象
- 数据封装类对象: Object, Array, Boolean, Number和String
- 其他对象: Function, Arguments, Math, Date, RegExp, Error
35.说几条写JavaScript的基本规范?
- 不要在同一行声明多个变量
- 请使用
===/!==
来比较true/false
或者数值- 使用对象字面量替代
new Array
这种形式- 不要使用全局函数
Switch
语句必须带有default
分支If
语句必须使用大括号for-in
循环中的变量 应该使用let
关键字明确限定作用域,从而避免作用域污
36.JavaScript创建对象的几种方式?
javascript
创建对象简单的说,无非就是使用内置对象或各种自定义对象,当然还可以用JSON
;但写法有很多种,也能混合使用
- 对象字面量的方式
person={firstname:"Mark",lastname:"Yun",age:25,eyecolor:"black"};
- 用
function
来模拟无参的构造函数function Person(){}var person=new Person();//定义一个function,如果使用new"实例化",该function可以看作是一个Classperson.name="Mark";person.age="25";person.work=function(){alert(person.name+" hello...");} person.work();
- 用
function
来模拟参构造函数来实现(用this
关键字定义构造的上下文属性)function Pet(name,age,hobby){this.name=name;//this作用域:当前对象this.age=age;this.hobby=hobby;this.eat=function(){alert("我叫"+this.name+",我喜欢"+this.hobby+",是个程序员");}}var maidou =new Pet("麦兜",25,"coding");//实例化、创建对象maidou.eat();//调用eat方法
- 用工厂方式来创建(内置对象)
var wcDog =new Object();wcDog.name="旺财";wcDog.age=3;wcDog.work=function(){alert("我是"+wcDog.name+",汪汪汪......");}wcDog.work();
- 用原型方式来创建
function Dog(){}Dog.prototype.name="旺财";Dog.prototype.eat=function(){alert(this.name+"是个吃货");}var wangcai =new Dog();wangcai.eat();
- 用混合方式来创建
function Car(name,price){this.name=name;this.price=price; }Car.prototype.sell=function(){alert("我是"+this.name+",我现在卖"+this.price+"万元");}var camry =new Car("凯美瑞",27);camry.sell();
37.eval是做什么的?
- 它的功能是把对应的字符串解析成JS代码并运行
- 应该避免使用eval,不安全,非常消耗性能(2次,一次解析成js语句,一次执行)
- 由JSON字符串转换为JSON对象的时候可以使用eval, var obj =eval('('+ str +')')
38.null和undefined的区别?
undefined
表示不存在这个值。
undefined
:是一个表示”无”的原始值或者说表示”缺少值”,就是此处应该有一个值,但是还没有定义。当尝试读取时会返回undefined
例如变量被声明了,但没有赋值时,就等于
undefined
null
表示一个对象被定义了,值为“空值”
null
: 是一个对象(空对象, 没有任何属性和方法)例如作为函数的参数,表示该函数的参数不是对象;
在验证
null
时,一定要使用===
,因为==
无法分别null
和undefined
39.['1', '2', '3'].map(parseInt)答案是什么?
[1, NaN, NaN]
因为parseInt
需要两个参数(val, radix)
,其中radix
表示解析时用的基数。map
传了3
个(element, index, array)
,对应的radix
不合法导致解析失败。
40.JavaScript代码中use strict是什么意思?使用它区别是什么?
use strict
是一种ECMAscript 5
添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行,使JS
编码更加规范化的模式,消除Javascript
语法的一些不合理、不严谨之处,减少一些怪异行为
41.js延迟加载的方式有哪些?
defer
和async
、动态创建DOM
方式(用得最多)、按需异步载入js
42.defer和async
defer
并行加载js
文件,会按照页面上script
标签的顺序执行async
并行加载js
文件,下载完成立即执行,不会按照页面上script
标签的顺序执行
43.说说严格模式的限制
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用
with
语句- 禁止
this
指向全局对象
44.attribute和property的区别?
- attribute是dom元素在文档中作为html标签拥有的属性
- Property是dom元素在js中作为对象有的属性
- 对于html的标准来说,attribute和Property是同步的,会自动更新的
- 但是对于自定义属性来说,他们是不同步的
45.es6怎么写class,为什么出现class这种东西?
- 这个语法糖可以让有OOP基础的人更快上手js,至少是一个官方的实现了
- 但对于熟悉js的入人来说,这个东西没啥大影响,一个Object.create也可以继承,也比class简洁
46.常见的兼容性问题?
png24
位的图片在iE6浏览器上出现背景,解决方案是做成PNG8
- 浏览器默认的
margin
和padding
不同。解决方案是加一个全局的*{margin:0;padding:0;}
来统一,,但是全局效率很低,一般是如下这样解决:IE
下,event
对象有x
,y
属性,但是没有pageX
,pageY
属性Firefox
下,event
对象有pageX
,pageY
属性,但是没有x,y
属性.
47.函数防抖节流原理
共同点:
防抖和节流都是防止函数被多次调用
区别:
- 如果用户一直触发这个函数,且每次触发函数的间隔小于设置的时间,防抖的情况只会调用一次
- 同样的操作,节流会每隔一定的时间调用一次函数
防抖(debounce):
n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
function debounce(func, wait, immediate=true) {let timeout, context, args;// 延迟执行函数const later = () => setTimeout(() => {timeout = nullif (!immediate) {func.apply(context, args);context = args = null;}}, wait);let debounced = function (...params) {if (!timeout) {timeout = later();if (immediate) {func.apply(this, params);} else {context = this;args = params;}} else {clearTimeout(timeout);timeout = later();}}debounced.cancel = function () {clearTimeout(timeout);timeout = null;};return debounced; };
防抖的应用场景:
- 每次resize/scroll触发统计事件
- 文本输入的验证(连续输入文字后发送ajax请求进行验证,验证一次就好)
节流(throttle):
高频事件在规定时间内只执行一次,执行一次后,只有大于设定的执行周期后才会执行第二次
//underscore.js function throttle(func, wait, options) {var timeout, context, args, result;var previous = 0;if (!options) options = {};var later = function () {previous = options.leading === false ? 0 : Date.now() || new Date().getTime();timeout = null;result = func.apply(context, args);if (!timeout) context = args = null;};var throttled = function () {var now = Date.now() || new Date().getTime();if (!previous && options.leading === false) previous = now;var remaining = wait - (now - previous);context = this;args = arguments;if (remaining <= 0 || remaining > wait) {if (timeout) {clearTimeout(timeout);timeout = null;}previous = now;result = func.apply(context, args);if (!timeout) context = args = null;} else if (!timeout && options.trailing !== false) {// 判断是否设置了定时器和 trailingtimeout = setTimeout(later, remaining);}return result;};throttled.cancel = function () {clearTimeout(timeout);previous = 0;timeout = context = args = null;};return throttled; };
函数节流的应用场景:
- DOM元素的拖拽功能实现(mousemove)
- 射击游戏的mousedown/keydown事件(单位时间内只能发射一颗子弹)
- 计算鼠标移动的距离(mousemove)
- Canvas模拟画板功能(mousemove)
- 搜索联想(keyup)
- 监听滚动事件判断是否到达页面底部加载更多:给scroll加了debounce后,只有用户停止滚动后,才会判断是否到达了页面底部;如果是throttle的话,只要页面滚动就会间隔一段时间判断一次
48.原始类型有哪几种,null是对象吗?
.js中5种原始数据类型
number:整数/小数/NaN
string:
boolean:
null:
undefined:Null类型是第二个只有一个值的数据类型,这个特殊的值是null,从逻辑角度来看,null值表示一个空对象指针,而这也正是使用typeof操作符检测null值会返回“object”的原因
49.为什么console.log(0.2+01==0.3)结果是false?
JavaScript 中的 number 类型就是浮点型,JavaScript 中的浮点数采用IEEE-754 格式的规定,这是一种二进制表示法,可以精确地表示分数,比如 1/2,1/8,1/1024,每个浮点数占 64 位。但是,二进制浮点数表示法并不能精确的表示类似 0.1 这样 的简单的数字,会有舍入误差。
由于采用二进制,JavaScript 也不能有限表示 1/10、1/2 等这样的分数。在二进制中,1/10(0.1)被表示为 0.00110011001100110011…… 注意 0011 是无限重复的,这是舍入误差造成的,所以对于 0.1 + 0.2 这样的运算,操作数会先被转成二进制,然后再计算
50.说一下js中类型转换的规则
值 转数字 转字符串 转布尔值 undefined NaN “undefined” false null 0 “null” false true 1 “true” false 0 “false” 0 “0” false -0 “0” false NaN “NaN” false Infinity “Infinity” true -Infinity ”-Infinity” true 1(非零) “1” true {}(任意对象) 见下文 见下文 true [](任意数组) 0 ”” true [9](包含一个数字元素) 9 “9” true [”a”](其他数组) NaN 使用.join()方法 true function(){}(任意函数) NaN 见下文 true Number的原始类型转换规则
- 数值转换后还是数值
- 字符串如果可以解析为数值则为数值, 空字符串为0, 无法解析的字符串为NaN
- 布尔转数值, true转为1, false转为0
- null转换为0
原始类型转换Number
Number的对象类型转换规则
传入实例M, 先调用M的
valueOf()
, 如果返回值V为基本数据类型, 则直接使用Number(V), 求最终返回值
如果T不属于基本数据类型, 则调用M的toString()
, 如果返回值S为基本数据类型, 则直接使用Number(S),求最后的结果, 如果S不属于基本数据类型, 则直接返回NaN对象类型转换1
对象类型转换2
String的原始类型转换规则
- 数值(Number)转为相应的字符串
- 字符串(String) 转换后还是字符串
- 布尔值(Boolean)转换规则: true => 'true', false=> 'false'
- undefine 转换为"undefine"
- null 转换为'null'
String原始类型转换
String 的对象类型转换规则
与Number的对象转换规则类似, 区别是: 先调用对象的toString(), 然后再调用valueOf()
其实正常情况下, 对象调用自身的toString()后, 对象就可以转换为string基本类型, valueOf() 没有机会被调用, 但万事有个例, 如果我们重新定义了对象的toString()方法,使其返回非基本类型的值, 那样就有机会调用对象的valueOf()方法了String对象类型转换规则
Boolean的原始类型转换 和 对象类型转换
undefined
,null
,NaN
,''
,-0
,+0
皆为false, 其余为true隐式类型转换
四则运算
+
,-
,*
,/
隐式类型转换之四则运算
51.深拷贝和浅拷贝的区别,如何实现?
浅拷贝:
- 只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
- 只复制对象的第一层属性
深拷贝:
- 另外创造一个一模一样的对象,新对象和原对象不共享内存,修改新对象不会改到原对象
- 对对象属性进行递归复制
浅拷贝实现:
1) 简单赋值:
function simpleClone(initalObj) { var obj = {}; for ( var i in initalObj) {obj[i] = initalObj[i];} return obj;}var obj = {a: "hello",b:{a: "world",b: 21},c:["Bob", "Tom", "Jenny"],d:function() {alert("hello world");}}var cloneObj = simpleClone(obj); console.log(cloneObj.b); console.log(cloneObj.c);console.log(cloneObj.d);cloneObj.b.a = "changed";cloneObj.c = [1, 2, 3];cloneObj.d = function() { alert("changed"); };console.log(obj.b);console.log(obj.c);console.log(obj.d);
2.Object.assign():
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
var obj = { a: {a: "hello", b: 21} };var initalObj = Object.assign({}, obj);initalObj.a.a = "changed";console.log(obj.a.a); // "changed"
注意:当object只有一层的时候,是深拷贝
深拷贝的实现:
1) 当对象至于一层时,使用Object.assign函数
2.JSON.parse(JSON.stringify(obj))
var obj1 = { body: { a: 10 } }; var obj2 = JSON.parse(JSON.stringify(obj1)); obj2.body.a = 20; console.log(obj1); // { body: { a: 10 } } <-- 沒被改到 console.log(obj2); // { body: { a: 20 } } console.log(obj1 === obj2); // false console.log(obj1.body === obj2.body); // false
封装:
var cloneObj = function(obj){var str, newobj = obj.constructor === Array ? [] : {};if(typeof obj !== 'object'){return;} else if(window.JSON){str = JSON.stringify(obj), //系列化对象newobj = JSON.parse(str); //还原} else {for(var i in obj){newobj[i] = typeof obj[i] === 'object' ? cloneObj(obj[i]) : obj[i]; }}return newobj; };
3) 递归拷贝:
function deepClone(initalObj, finalObj) { var obj = finalObj || {}; for (var i in initalObj) { var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况if(prop === obj) { continue;} if (typeof prop === 'object') {obj[i] = (prop.constructor === Array) ? [] : {}; arguments.callee(prop, obj[i]);} else {obj[i] = prop;}} return obj; } var str = {}; var obj = { a: {a: "hello", b: 21} }; deepClone(obj, str); console.log(str.a);
4) 使用Object.create方法
直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。
function deepClone(initalObj, finalObj) { var obj = finalObj || {}; for (var i in initalObj) { var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况if(prop === obj) { continue;} if (typeof prop === 'object') {obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);} else {obj[i] = prop;}} return obj; }
5) jQuery的$.extend可以用来做 Deep Copy
var $ = require('jquery'); var obj1 = {a: 1,b: { f: { g: 1 } },c: [1, 2, 3] }; var obj2 = $.extend(true, {}, obj1); console.log(obj1.b.f === obj2.b.f); // false
6) lodash
另外一个很热门的函数库lodash,也有提供_.cloneDeep用来做 Deep Copy。
var _ = require('lodash'); var obj1 = {a: 1,b: { f: { g: 1 } },c: [1, 2, 3] }; var obj2 = _.cloneDeep(obj1); console.log(obj1.b.f === obj2.b.f); // false
这个性能还不错,使用起来也很简单。
52.如何判断this?箭头函数的this是什么?
1) 谁作为拥有者调用它就指向谁
2) bind谁就指向谁
3) 没有拥有者,直接调用,指向window
4) call谁就是谁,apply谁,其实bind就是通过call,apply实现的
5) 箭头函数不绑定this, 会捕获所在的上下文的this值,作为自己的this值
53.==和===的区别?
===
三个等号我们称为等同符,当等号两边的值为相同类型的时候,直接比较等号两边的值,值相同则返回true,若等号两边的值类型不同时直接返回false。
例:100===“100” //返回false
abc===“abc” //返回false
‘abc’===“abc” //返回true
NaN===NaN //返回false
false===false //返回true
==
两个等号我们称为等值符,当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。
54.什么是闭包?
概念:
- 闭包就是能够读取其他函数内部变量的函数
- 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的常见方式就是在一个函数中创建另外一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用域链
闭包的特性:
- 函数内部再嵌套函数
- 内部函数可以引用外层的参数和变量
- 参数和变量不会被垃圾回收机制回收(解决方式: 使用完毕之后将变量设置为null)
对闭包的理解:
- 使用闭包主要是为了射击私有的方法和变量.闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄漏
- 在js中,函数就是闭包,只有函数才会产生作用域的概念
- 闭包的另一个用处,是封装对象的私有属性和私有方法
优点:
实现封装和缓存等
缺点:
消耗内存,不正当使用会造成内存溢出的问题
注意事项:
- 由于闭包会使函数中的变量被保存在内存中,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄漏
- 解决方案: 在退出函数之前,将不使用的局部变量全部删除
55.JavaScript原型,原型链?有什么特点?
概念:
- 每个对象都会在其内部初始化一个属性,就是prototype(原型)
- 当我们访问一个对象属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里查找这个属性,这个prototype又会有自己的prototype.于是就这样一直找下去,也就是我们平时所说的原型链的概念
- 关系:instance.constructor.prototype=instance.__proto__
特点:
- JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本.当我们修改原型时,与之相关的对象也会继承这一改变
- 当我们需要一个属性时,JavaScript引擎会先看当前对象中是否有这个属性,如果没有的,就会查找他的prototype对象是否有这个属性,如此递推下去,一直检索到Object内建对象
56.typeof ()和instanceof()用法的区别?
typeof():
- 是一个一元运算,放在一个运算数之前,运算数可以是任意类型
- 它返回值是一个字符串,该字符串说明运算数的类型
instance():
- 运算符用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链上.
- 通常来讲,使用instance就是判断一个实例是否属于某种类型
57.什么是变量提升?
变量提升:
- 函数声明和变量声明总是会被提升到变量的最顶端
- JavaScript只有声明的变量会提升,初始化的不会
如下:
y 输出了undefined,这是因为变量声明 (var y) 提升了,但是初始化(y = 7) 并不会提升,所以 y 变量是一个未定义的变量。
var x = 5; // 初始化 x var y; // 声明 yelem = document.getElementById("demo"); // 查找元素 elem.innerHTML = x + " " + y; // 显示 x 和 yy = 7; // 设置 y 为 7
1) 函数声明会被提升,函数表达式不会
//函数声明, 形如: function show() {}// 函数表达式: var show=function{}
2) 出现同名的函数声明,变量声明的时候,函数声明会被优先提升,变量声明会被忽略
show(); //你好var show;function show(){console.log( '你好' );}show = function(){console.log( 'hello' );}
上面这段代码,结果为什么会是 '你好'?
当出现同名的函数声明,变量声明的时候, 函数声明会被优先提升,变量声明会被忽略。经过编译之后,变为:
function show(){console.log( '你好' );}show(); //你好show = function(){console.log( 'hello' );}show();//如果这里在调用一次,就是hello, 因为show函数体在执行阶段 被 重新赋值了
3) 如果有同名的函数声明,后面的会覆盖前面的
show(); //how are youvar show;function show(){console.log( 'hello' );} show = function(){console.log( '你好' );}function show(){console.log( 'how are you!' );}
//上面的代码经过编译之后,变成如下形式:function show(){console.log( 'how are you!' );}show(); //how are youshow = function(){console.log( '你好' );}show(); //如果在这里再执行一次,结果:你好
58.call,apply以及bind函数内部实现是怎么样的?
相同点:
call,apply,bind都是改变函数的上下文,说的直白一点就是改变了函数this的指向
不同点:
- call和apply改变了函数的this,并且执行了函数
- bind是改变了函数的this,并返回一个函数,但不执行该函数
call示例:
var doThu = function(a, b) {console.log(this)console.log(this.name)console.log([a, b]) } var stu = {name: 'xiaoming',doThu: doThu, } stu.doThu(1, 2) // stu对象 xiaoming [1, 2] doThu.call(stu, 1, 2) // stu对象 xiaoming [1, 2]
- 由此可见,在stu上添加一个属性doThu,再执行这个函数,就将doThu的this指向了stu。
- 而call的作用就与此相当,只不过call为stu添加了doThu方法后,执行了doThu,然后再将doThu这个方法从stu中删除。
call函数的内部实现原理:
Function.prototype.call = function(thisArg, args) {// this指向调用call的对象if (typeof this !== 'function') { // 调用call的若不是函数则报错throw new TypeError('Error')}thisArg = thisArg || windowthisArg.fn = this // 将调用call函数的对象添加到thisArg的属性中const result = thisArg.fn(...[...arguments].slice(1)) // 执行该属性delete thisArg.fn // 删除该属性return result }
aaply的实现原理和call一样,只不过传入的参数不同而已:
Function.prototype.apply = function(thisArg, args) {if (typeof this !== 'function') { throw new TypeError('Error')}thisArg = thisArg || windowthisArg.fn = thislet resultif(args) {result = thisArg.fn(...args)} else {result = thisArg.fn()}delete thisArg.fnreturn result }
bind的原理比call和apply更复杂,bind需要考虑一些复杂的边界条件.bind后的函数会返回一个函数,而这个函数页可能被用来实例化
Function.prototype.bind = function(thisArg) {if(typeof this !== 'function'){throw new TypeError(this + 'must be a function');}// 存储函数本身const _this = this;// 去除thisArg的其他参数 转成数组const args = [...arguments].slice(1)// 返回一个函数const bound = function() {// 可能返回了一个构造函数,我们可以 new F(),所以需要判断if (this instanceof bound) {return new _this(...args, ...arguments)}// apply修改this指向,把两个函数的参数合并传给thisArg函数,并执行thisArg函数,返回执行结果return _this.apply(thisArg, args.concat(...arguments))}return bound }
59.为什么会出现setTimeout倒计时误差?如何减少?
<!DOCTYPE html> <html><head><meta charset="utf-8"><title></title></head><body><p id="message"></p></body><script type="text/javascript">var message = document.getElementById("message");var count = 1000;function animate() {var start = +new Date();message.innerHTML = count--;var finish = +new Date();setTimeout(animate, 1000 - (finish-start));}animate();</script> </html>
本例实现了倒数1000秒的功能,我们在使用setTimeout的时候如果要一个函数每每隔1秒执行一次就会这样写
setTimeout(animate, 1000);
但是这样会忽略animate方法本身的运行时间,所以我们可以在执行animate方法的时候计算这个方法主要的语句的执行时间,之后在setTimeout中减去那个由于运行语句而耽搁的时间,从而实现更加精确的计时使用requestAnimationFrame可以减少倒计时误差:
幕刷新设计的函数,它会在浏览器准备重绘之前调用指定的函数,这样可以保证动画或倒计时的更新与屏幕刷新同步,从而减少误差。requestAnimationFrame的优点包括:
- 高精度计时:它能够更好地适应屏幕的刷新率,提供更平滑的动画效果。
- 节能优化:当页面不可见时,requestAnimationFrame会停止执行,从而节省CPU资源。
- 避免丢帧现象:在处理高频率事件时,requestAnimationFrame可以保证每个刷新间隔内函数只执行一次,提高效率4。
示例代码展示如何使用requestAnimationFrame减少倒计时误差:
let startTime = Date.now(); let requestId = null; let endTime = startTime + 10000; // 设置10秒倒计时function updateTime() {let currentTime = Date.now();if (currentTime < endTime) {requestId = requestAnimationFrame(updateTime);console.log('剩余:', (endTime-currentTime)/1000, 's');} else {console.log("倒计时结束");cancelAnimationFrame(requestId);requestId = null;} }requestId = requestAnimationFrame(updateTime);
60.对js执行上下文栈和作用域链的理解?
执行上下文:
1) 当前 JavaScript 代码被解析和执行时所在环境, JS执行上下文栈可以认为是一个存储函数调用的栈结构
2) 遵循先进后出的原则。
- JavaScript执行在单线程上,所有的代码都是排队执行。
- 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。
- 每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。
- 当前函数执行-完成后,当前函数的执行上下文出栈,并等待垃圾回收。
- 浏览器的JS执行引擎总是访问栈顶的执行上下文。
- 全局上下文只有唯一的一个,它在浏览器关闭时出栈。
作用域链:
无论是 LHS 还是 RHS 查询,都会在当前的作用域开始查找,如果没有找到,就会向上级作用域继续查找目标标识符,每次上升一个作用域,一直到全局作用域为止。
61.new的原理是什么?通过new的方式创建的对象和通过字面量创建有什么区别?
new:
- 创建一个新对象
- 这个新对象会被执行原型连接
- 将构造函数的作用域赋值给新对象,即this指向这个新对象
- 如果函数的作用域赋值给新对象,即this指向这个新对象
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
function new(func) {lat target = {};target.__proto__ = func.prototype;let res = func.call(target);if (typeof(res) == "object" || typeof(res) == "function") {return res;}return target; }
- 字面量创建对象,不会调用Object构造函数,简洁且性能更好
- new Object()方式创建对象本质上是方法调用,涉及到在proto链中遍历该方法
- 当找到该方法后,又会生产方法调用必须得堆栈信息,方法调用结束后,还要释放该堆栈,性能不如字面量的方式
- 通过对象字面量定义对象时,不会调用Object构造函数
62.prototype和__proto__的区别?
prototype
是构造函数的属性
__proto__
是每个实例都有的属性,可以访问prototype属性
实例的__proto__与其构造的prototype指向的是同一个对象
function Student(name) {this.name = name; } Student.prototype.setAge = function(){this.age=20; } let Jack = new Student('jack'); /** console.log(Jack.__proto__); console.log(Object.getPrototypeOf(Jack)); console.log(Student.prototype); 都是: {setAge: ƒ} setAge: ƒ () constructor: ƒ Student(name) [[Prototype]]: Object */ console.log(Jack.__proto__); console.log(Object.getPrototypeOf(Jack)); console.log(Student.prototype);console.log(Jack.__proto__ === Student.prototype);//true
63.使用es5实现一个继承
寄生组合继承:
function SuperType(name) {this.name = name;this.colors = ['red', 'blue', 'green']; }SuperType.prototype.sayName = function() {console.log(this.name); };function SubType(name, age) {SuperType.call(this, name);this.age = age; }SubType.prototype.sayAge = function() {console.log(this.age); };(function() {var Super = function() {};Super.prototype = SuperType.prototype;SubType.prototype = new Super(); })();SubType.prototype.constructor = SubType;var sub1 = new SubType('alice', 12); console.log(sub1); //SubType {name: 'alice', colors: Array(3), age: 12}
64.取数组的最大值(es5,es6)
// ES5 的写法 Math.max.apply(null, [14, 3, 77, 30]);// ES6 的写法 Math.max(...[14, 3, 77, 30]);// reduce [14,3,77,30].reduce((acc, cur)=>{return acc= acc > cur ? acc : cur });
65.es6新特性有哪些?
- 新增块级作用域:let, const
- 类的语法糖: class
- 基本数据类型: Symbol
- 变量解构赋值
- 函数参数允许设置默认值,引入rest参数,新增箭头函数
- 数增了一些api,如isArray/from,of方法;数组新增了entries(),keys(),values()方法
- 对象和数组新增扩展运算符
- es6新增了Set和Map数据结构
- es6原生提供了Proxy构造函数,用来生成Proxy实例
- es6新增了生成器(Generator)和遍历函数(Iterator)
66.Promise有几种状态,Promise有什么缺点?
Promise有三种状态:
fullfilled, rejected, pending
Promise优点:
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果
- 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
Promise缺点:
- 无法取消Promise
- 当处于pending状态时,无法得知目前进展到哪一阶段
67.Promise构造函数是同步还是异步,then呢?Promise如何实现then处理
Promise构造函数同步执行的,then是异步执行的
68.Promise和setTimeout的区别?
Promise是微任务,setTimeout是宏任务,同一个事件循环中,Promise.then总是先于setTimeout执行
69.如何实现Promise.all?
要实现Promise.all,首先我们要知道Promise.all的功能:
- 如果传入的参数是一个空的可迭代对象,那么Promise对象回调完成(resolve),只有此情况,是同步执行的,其他都是异步返回的
- 如果传入的参数不包含任何Promise,则返回一个异步完成
Promise中所有的promise都完成时或参数中不包含promise时回调完成
- 如果参数中有一个promise失败,那么Promise.all返回的promise对象失败
- 在任何情况下,Promise.all返回的Promise的完成状态的结果都是一个数组
Promise.all = function (promises) {return new Promise((resolve, reject) => {let index = 0;let result = [];if (promises.length === 0) {resolve(result);} else {setTimeout(() => {function processValue(i, data) {result[i] = data;if (++index === promises.length) {resolve(result);}}for (let i = 0; i < promises.length; i++) {//promises[i] 可能是普通值Promise.resolve(promises[i]).then((data) => {processValue(i, data);}, (err) => {reject(err);return;});}})}}); }
常见面试问题: 如何让promise.all 抛出异常后依然有效
处理多个并发请求时,我们一般会用Promise.all()方法。
该方法指当所有在可迭代参数中的 promises 已完成,或者第一个传递的 promise(指 reject)失败时,返回 promise。
但是当其中任何一个被拒绝的话。Promise.all([…])就会立即被拒绝,并丢弃来自其他所有promis的全部结果。
也就是说,promise.all 中任何一个 promise 出现错误的时候都会执行reject,导致其它正常返回的数据也无法使用。
如何让Promise.all在抛出异常后依然有效呢?
方案一:在promise.all队列中使用map每一个过滤每一个promise任务,其中任意一个报错后,return一个返回值
在promise.all队列中,使用map每一个过滤每一个promise任务,其中任意一个报错后,return一个返回值,确保promise能正常执行走到.then中。效果图:
示例代码:// 模拟返回示例 function getData(api) {return new Promise((resolve, reject) => {setTimeout(() => {Math.random() > 0.5 ? resolve('get ' + api + ' success') : reject('error')}, 200)}) } function getDatas(arr) {return Promise.all(arr.map(one => one.catch(e => e))).then(values => values).catch(error => error) }async function initData() {let result = await getDatas([getData('./api1'), getData('./api2'), getData('./api3')]) //模拟请求console.log(result); }initData()
方案二:使用 Promise.allSettled 替代 Promise.all()。Promise.allSettled()方法返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果。
let allSettled = Promise.allSettled([getData('./api1'), getData('./api2'), getData('./api3')]) //模拟请求allSettled.then(res => {console.log(res)})
70.如何实现Promise.finally
不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then,并且会将值原封不动的传递给后面的then
Promise.prototype.finally = function (callback) {return this.then((value) => {return Promise.resolve(callback()).then(() => {return value;});}, (err) => {return Promise.resolve(callback()).then(() => {throw err;});});
71.如何判断img加载完成
onload事件
一、load事件<!DOCTYPE HTML><html> <head> <meta charset="utf-8"><title>img - load event</title></head> <body><img id="img1" src="http://pic1.win4000.com/wallpaper/f/51c3bb99a21ea.jpg"><p id="p1">loading...</p><script type="text/javascript">img1.onload = function() {p1.innerHTML = 'loaded'}</script></body></html>
测试,所有浏览器都显示出了“loaded”,说明所有浏览器都支持img的load事件。
readystatechange事件
<!DOCTYPE HTML><html> <head> <meta charset="utf-8"><title>img - readystatechange event</title></head> <body><img id="img1" src="http://pic1.win4000.com/wallpaper/f/51c3bb99a21ea.jpg"><p id="p1">loading...</p><script type="text/javascript">img1.onreadystatechange = function() {if(img1.readyState=="complete"||img1.readyState=="loaded"){ p1.innerHTML = 'readystatechange:loaded'}}</script></body></html>
readyState为complete和loaded则表明图片已经加载完毕。测试IE6-IE10支持该事件,其它浏览器不支持。
三、img的complete属性
<!DOCTYPE HTML><html> <head> <meta charset="utf-8"><title>img - complete attribute</title></head> <body><img id="img1" src="http://pic1.win4000.com/wallpaper/f/51c3bb99a21ea.jpg"><p id="p1">loading...</p><script type="text/javascript">function imgLoad(img, callback) {var timer = setInterval(function() {if (img.complete) {callback(img)clearInterval(timer)}}, 50)}imgLoad(img1, function() {p1.innerHTML('加载完毕')})</script></body></html>
轮询不断监测img的complete属性,如果为true则表明图片已经加载完毕,停止轮询。该属性所有浏览器都支持。
72.如何阻止冒泡
冒泡事件:
事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发
w3c的方法是e.stopPropagation(),ie则是e.cancelBubble=true
//阻止冒泡行为 function stopBubble(e) { //如果提供了事件对象,则这是一个非IE浏览器 if ( e && e.stopPropagation ) //因此它支持W3C的stopPropagation()方法 e.stopPropagation(); else //否则,我们需要使用IE的方式来取消事件冒泡 window.event.cancelBubble = true; }
73.如何阻止默认事件
w3c的方法是e.preventDefault(),IE则是使用e.returnValue = false
//阻止浏览器的默认行为 function stopDefault( e ) { //阻止默认浏览器动作(W3C) if ( e && e.preventDefault ) e.preventDefault(); //IE中阻止函数器默认动作的方式 else window.event.returnValue = false; return false; }
74.ajax请求时,如何解释json数据
使用eval parse,但是鉴于安全性考虑,使用parse更靠谱
75.json和jsonp的区别?
- json返回的是一串json数据;而jsonp返回的是脚本代码(包含一个函数调用)
- jsonp的全名叫做json with padding,就是把json对象用符合js语法的形式包裹起来以使其他的网站可以请求到,也就是将json封装成js文件传过去
76.如何使用原生js给按钮绑定两个click事件?
Var btn=document.getElementById(‘btn’); //事件监听 绑定多个事件 var btn4 = document.getElementById("btn4"); btn4.addEventListener("click",hello1); btn4.addEventListener("click",hello2); function hello1(){alert("hello 1"); } function hello2(){alert("hello 2"); }
77.拖拽会用到哪些事件?
- dragstart: 拖拽开始时在被拖拽元素上触发此事件,监听器需要设置拖拽所需的数据,从操作系统拖拽文件到浏览器时不触发此事件
- dragenter: 拖拽鼠标进入元素时在该元素上触发,用于给拖放元素设置视觉反馈,比如高亮
- dragover: 拖拽时鼠标在目标元素上移动时触发.监听器通过阻止浏览器默认行为设置元素为可拖放元素
- dragleave: 拖拽时鼠标移出目标元素时在目标元素上触发,此时监听器可以取消掉前面设置的视觉效果
- drag: 拖拽期间在被拖拽元素上连续触发
- drop: 鼠标在拖放目标上释放时,在拖放目标上触发,此时监听器需要收集数据并且执行所需操作,如果是从操作系统拖放文件到浏览器,需要取消浏览器默认行为
- dragend: 鼠标在拖放目标上释放时,在拖放元素上触发,将元素从浏览器拖放到操作系统时不会触发此事件
78.document.write和innerHTML的区别?
- document.write是直接写入到页面的内容流,如果在写之前没有调用document.open, 浏览器会自动调用open。每次写完关闭之后重新调用该函数,会导致页面被重写。
- innerHTML则是DOM页面元素的一个属性,代表该元素的html内容。你可以精确到某一个具体的元素来进行更改。如果想修改document的内容,则需要修改document.documentElement.innerElement。
- innerHTML将内容写入某个DOM节点,不会导致页面全部重绘
- innerHTML很多情况下都优于document.write,其原因在于其允许更精确的控制要刷新页面的那一个部分。
79.jQuery的事件委托方法bind 、live、delegate、on之间有什么区别?
(1)、bind
定义和用法:主要用于给选择到的元素上绑定特定事件类型的监听函数;
语法:bind(type,[data],function(eventObject));
特点:
(1)、适用于页面元素静态绑定。只能给调用它的时候已经存在的元素绑定事件,不能给未来新增的元素绑定事件。
(2)、当页面加载完的时候,你才可以进行bind(),所以可能产生效率问题。
实例如下:$( "#members li a" ).bind( "click", function( e ) {} );
(2)、live
定义和用法:主要用于给选择到的元素上绑定特定事件类型的监听函数;
语法:live(type, [data], fn);
特点:
(1)、live方法并没有将监听器绑定到自己(this)身上,而是绑定到了this.context上了。
(2)、live正是利用了事件委托机制来完成事件的监听处理,把节点的处理委托给了document,新添加的元素不必再绑定一次监听器。
(3)、使用live()方法但却只能放在直接选择的元素后面,不能在层级比较深,连缀的DOM遍历方法后面使用,即$(“ul”").live...可以,但$("body").find("ul").live...不行;
实例如下:$( document ).on( "click", "#members li a", function( e ) {} );
(3)、delegate
定义和用法:将监听事件绑定在就近的父级元素上
语法:delegate(selector,type,[data],fn)
特点:
(1)、选择就近的父级元素,因为事件可以更快的冒泡上去,能够在第一时间进行处理。
(2)、更精确的小范围使用事件代理,性能优于.live()。可以用在动态添加的元素上。
实例如下:
$("#info_table").delegate("td","click",function(){/显示更多信息/});
$("table").find("#info").delegate("td","click",function(){/显示更多信息/});
(4)、on
定义和用法:将监听事件绑定到指定元素上。
语法:on(type,[selector],[data],fn)
实例如下:$("#info_table").on("click","td",function(){/显示更多信息/});参数的位置写法与delegate不一样。
说明:on方法是当前JQuery推荐使用的事件绑定方法,附加只运行一次就删除函数的方法是one()。
总结 :.bind(), .live(), .delegate(),.on()分别对应的相反事件为:.unbind(),.die(), .undelegate(),.off()
80.浏览器是如何渲染页面的
渲染的流程如下:
- 解析HTML文件,创建dom树--自上而下,遇到任何样式(link,style)与脚本(script)都会阻塞(外部样式不阻塞后续外部脚本的加载)
- 解析css.优先级: 浏览器默认设置 < 用户设置 < 外部样式 < 内联样式 < HTML中的style样式
- 将css和dom合并,构建渲染树(render tree)
- 布局和绘制,重绘(repait)和重排(reflow)
81.$(document).ready()方法和window.onload有什么区别?
(1)、window.onload方法是在网页中所有的元素(包括元素的所有关联文件)完全加载到浏览器后才执行的。
(2)、$(document).ready() 方法可以在DOM载入就绪时就对其进行操纵,并调用执行绑定的函数。
82.jQuery中$.get()提交和$.post()提交有区别吗?
相同点:
都是异步请求的方式来获取服务端的数据
不同点:
- 请求方式不同: $.get()方法使用GET方法来进行异步请求的,$.post()方法使用POST方法来进行异步请求的
- 参数传递方式不同: get请求会将参数跟在URL后进行传递,而POST请求则是作为HTTP消息的实体内容发送给web服务器的,这种传递是对用户不可见的
- 数据传输大小不同: get方式传输的数据大小不超过2KB而POST大的多
- 安全问题: GET方式请求的数据会被浏览器缓存起来,因此有安全问题
83.对前端路由的理解?
前端的路由和后端的路由在实现技术上不一致,但是原理是一样的。在html5的history api出现之前,前端的路由是通过hash实现的,hash能够兼容低版本的浏览器
服务端路由:
每跳转到不同的url,都是重新访问服务端,然后服务端返回页面,页面也可以是服务端获取数据,然后和模板组合,返回html,也可以是直接返回模板html,然后由前端js再去请求数据,使用前端模板和数据进行组合,生成想要的html
前端路由:
每跳转到不同的url都是使用前端的描点路由,实际上只是js根据url来操作dom元素,根据每个页面需要的去服务端请求数据,返回数据后和模板进行组合,当然模板可能是请求服务端返回的,这就是SPA单页程序
在js可以通过window.location.hash读取路径, 解析之后就可以响应不同路径的逻辑处理
history是HTML5才有的新API,可以用来操作浏览器的session history(会话历史),基于history来实现的路由可以和最初的例子中提到的路径规则一样
html5还新增了一个hashchange事件,也是很有用的一个新事件:
当页面hash发生变化时,会触发hashchange,锚点hash起到引导浏览器这次记录推入历史记录栈顶的作用
window.location.hash的改变不会重新加载页面,而是将之当成新页面,放入历史栈里.并且,当前进或后退或者触发hashchange事件时,我们可以在对应事件处理函数中注册ajax等操作
但是hashchange这个事件不是每个浏览器都支持的,低级浏览器需要使用轮询检测url是否在变化,来检测描点的变化.
当锚点内容(location.hash)被操作时,如果锚点内容发生改变浏览器才会将其放入历史栈中,如果锚点内容没有发生变化,历史栈并不会增加,并且不会触发hashchange事件
84.手写一个类的继承?
function Animal(name){this.name = name;this.eat = function(){console.log(this.name + "吃饭");} }function Cat(name){Animal.call(this, name); }Cat.prototype = Object.create(Animal.prototype); Cat.prototype.constructor = Cat var cat = new Cat("maomi"); cat.name; cat.eat();
85.XMLHttpRequest.readyState状态码的意思
状态码readyState
当一个XMLHttpRequest初次创建时,这个属性的值是从0开始,直到接收完整的http响应,这个值增加到4.
有5种状态:
状态0: 请求初始 化
XMLHttpRequest对象已经创建或者已被abort()方法重置,但还没有调用open方法
状态1: 载入服务器连接已经建立
已调用open方法,但是send方法还未调用
状态2: 载入完成,请求已接收
send方法已调用,http请求已发送到web服务器,请求已经发送完成,未接收到响应
状态3: 交互,请求处理中
所有响应头部都已经接收到,响应体开始接收但未完成,即可以接收到部分响应数据;
状态4: 请求完成,且响应已就绪
已经接收到全部数据,并且连接已经关闭
86.正则表达式常见面试题
86-1给一个连字符串例如:get-element-by-id转化成驼峰形式。
var str = "get-element-by-id"; var reg = /-\w/g; // 匹配横杆以及之后的一个字符,全局匹配 console.log(str.replace(reg,function($0){return $0.slice(1).toUpperCase();// 匹配到到是-e -b -i 形式截取后一个字符转成大写 }));
function toCamelCase(str) {return str.split('-') // 将字符串按连字符分割成数组.map((word, index) => {if (index === 0) {return word.toLowerCase(); // 第一个单词保持小写}return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); // 其他单词首字母大写}).join(''); // 将数组重新拼接成字符串 }const result = toCamelCase('get-element-by-id'); console.log(result); // 输出: getElementById
86-2匹配二进制数字
var str = "10101111"; var reg = /^[01]+$/g; console.log(reg.test(str));
86-3非零数字的十进制数字(至少一位数字,但不能以0开头)
var str = "81"; var reg = /^[1-9][0-9]?$/g; console.log(reg.test(str));
86-4匹配一年中的12月
var str = "12"; var reg = /^(0?[1-9]|1[0-2])$/g; console.log(reg.test(str));
86-5匹配qq号最长13位
var str ="10009093283333"; var reg = /^[1-9][0-9]{4,12}$/g; console.log(reg.test(str));
86-6匹配常见的固定电话号码
var str = "000-12344562"; // \(? 匹配左括号一次或0次然后以0开头后面加两个数字,再匹配右括号或空格或减号一次或0次,随后匹配8个数字 var reg = /\(?0\d{2}[) -]?\d{8}/g; console.log(str.match(reg));
86-7匹配ip地址
var str = "255.221.221.12"; // [01]?\d\d?表示匹配小于199的数,可以说两位数或一位数,2[0-4]\d表示从200到249,配合25[0-5]就表示小于255的数了。 var reg = /(([01]?\d\d?|2[0-4]\d|25[0-5])\.){3}([01]?\d\d?|2[0-4]\d|25[0-5])/g; console.log(str.match(reg));
86-8匹配使用尖括号括起来的以a开头的字符串
var str = "<a herf='www.baidu.com'>"; var reg = /<a[^>]+>/g; console.log(str.match(reg));
86-9分割数字每三个以一个逗号划分
var str = "12345678901"; function numSplit(str){var re = /(\d)(?=(\d{3})+$)/g;//(\d{3})+$ 的意思是连续匹配 3 个数字,且最后一次匹配以 3 个数字结尾。//要找到所有的单个字符,这些字符的后面跟随的字符的个数必须是3的倍数,并在符合条件的单个字符后面添加,return str.replace(re,'$1,'); } console.log(numSplit(str));
86-10 判断字符串是否包含数字
function containsNumber(str) {var regx = /\d/;return regx.text(str); }
86-11判断电话号码
function isPhone(tel) {var regx = /^1[34578]\d{9}$/;return regx.test(tel);
}
86-12判断是否符合指定格式
给定字符串str,检查其是否符合如下格式
- XXX-XXX-XXXX
- 其中X为Number类型
function matchesPattern(str) {return /^(\d{3}-){2}\d{4}&/.test(str); }
86-13.判断是否符合USD格式
给定字符串 str,检查其是否符合美元书写格式
- 以 $ 开始
- 整数部分,从个位起,满 3 个数字用 , 分隔
- 如果为小数,则小数部分长度为 2
- 正确的格式如:$1,023,032.03 或者 $2.03,错误的格式如:$3,432,12.12 或者 $34,344.3**
function isUSD(str) {var regx = /^\$\d{1,3}(,\d{3})*(\.\d{2})?$/;return regx.test(str); }
86-14.js实现千位分隔符
相关概念:
?=
是一个正向肯定预查(positive lookahead)的语法。它用于匹配某个位置,该位置后面紧跟着指定的模式,但不包括该模式本身。换句话说,?=
用于检查某个模式是否存在,但不会消耗字符,即不会将其作为匹配结果的一部分。$&表示与regx相匹配的字符串
注意事项
- 正向肯定预查
(?=expression)
不会消耗字符,即不会将其作为匹配结果的一部分。- 如果需要匹配某个模式且该模式本身也包含在结果中,可以使用捕获组
( )
。对比
- 正向肯定预查
(?=expression)
:匹配某个位置,该位置后面紧跟着指定的模式,但不包括该模式本身。- 正向否定预查
(?!expression)
:匹配某个位置,该位置后面不跟着指定的模式。- 负向肯定预查
(?<=expression)
:匹配某个位置,该位置前面紧跟着指定的模式,但不包括该模式本身(ES2018 引入)。- 负向否定预查
(?<!expression)
:匹配某个位置,该位置前面不跟着指定的模式(ES2018 引入)。function format(number) {var regx = /\d{1,3}(?=(\d{3})+$)/g;return (number + '').replace(regx, '$&,') // $&表示与regx相匹配的字符串 }
86-15获取url参数
获取 url 中的参数
- 指定参数名称,返回该参数的值 或者 空字符串
- 不指定参数名称,返回全部的参数对象 或者 {}
- 如果存在多个同名参数,则返回数组
function getUrlParam(url, key) {var arr = {};url.replace(/\??(\w+)=(\w+)&?/g, function(match, matchKey, matchValue) {if (!arr[matchKey]) {arr[matchKey] = matchValue;} else {var temp = arr[matchKey];arr[matchKey] = [].concat(temp, matchValue);}});if (!key) {return arr;} else {for (ele in arr) {if (ele = key) {return arr[ele];}}return '';} }
86-16验证邮箱
function isEmail(email) {var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;return regx.test(email);
}
86-17验证身份证号码
身份证号码可能为15位或18位,15位为全数字,18位中前17位为数字,最后一位为数字或者X
function isCardNo(number) {var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;return regx.test(number); }
86-18匹配汉字
var regx = /^[\u4e00-\u9fa5]{0,}$/;
86-19去除首尾的"/"
var str = '/asdf//';
str = str.replace(/^\/*|\/*$/g, '');
86-20.判断日期格式是否符合 '2017-05-11'的形式,简单判断,只判断格式
var regx = /^\d{4}\-\d{1,2}\-\d{1,2}$/
86-21.判断日期格式是否符合 '2017-05-11'的形式,严格判断(比较复杂)
var regx = /^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$/;
86-22.ipv4地址正则
var regx = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
86.23.十六进制颜色正则
var regx = /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/;
86-24.车牌号正则
var regx = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/;
86-25.过滤HTML标签
var str="<p>dasdsa</p>nice <br> test</br>"
var regx = /<[^<>]+>/g;
str = str.replace(regx, '');
86-26.密码强度正则,最少6位,包括至少1个大写字母,1个小写字母,1个数字,1个特殊字符
var regx = /^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$/;
86-27.URL正则
var regx = /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
86-28.匹配浮点数
var regx = /^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$/;
87.js编程
87-1.请写出代码的运行结果?如何改进?
for(var i=0;i<5;i++){setTimeout(function(){console.log(i)},1000)}
//输出结果为(五个5)
原因是:
settimeout是异步执行,1s后往任务队列里面添加一个任务, 只有主线上的全部执行完,才会执行任务队列里的任务, 当主线执行完成后,i是5,所以此时再去执行任务队列里的任务时,i全部是5了。
改进
for(let i=0;i<5;i++){setTimeout(function(){console.log(i)},1000)}
//输出 0,1,2,3,4
解析:for循环头部的let不仅将i绑定到for循环块中, 它也将其重新绑定到 **循环体的每一次迭代** 中,确保上一次迭代结束的值重新被赋值。setTimeout里面的function()属于一个新的域, 通过 var 定义的变量是无法传入到这个函数执行域中的, 而通过使用 let 来声明块变量,这时候变量就能作用于这个块, 所以 function就能使用 i 这个变量了;
87-2.请写出打印结果,并解释为什么?
let length = 10;function fn(){console.log(this.length); } var obj = {length:5,method:function(){return fn;} } obj.method()();
//输出结果:0
解析:
"this永远指向调用他的对象",在执行obj.method()方法时,如果函数内部有this,则this确实是指向obj,但是method()内部执行的是fn()函数,而fn()函数绑定的对象是window,即window.fn()
所以this指的事window
但是为什么没有显示10而是显示的0是因为
let声明变量会形成块级作用域,且不存在声明提升,而var存在声明提升。所以当使用let声明变量时,不存在声明提升,length属性实际上并没有添加到window对象中。
87-3.请写出打印结果,并解释为什么?
var name = "clobal";var object = {name:'local',fn:function(){return this.name;},getName:function(){alert("1." + this.name);return function(){return this.name;}},getFn:function(){return this.fn;}};var fun = object.getName();alert("2."+fun());alert("3."+fun.apply(object));alert("4."+fun.call(object));var fn = object.getFn();alert("5."+fn())
输出结果
1.local 2.clocal 3.local 4.local 5.clocal
解析
this对象是在运行时基于函数的执行环境绑定的,
匿名函数的执行环境具有全局性,
因此匿名函数的this指向window
88.重写map方法
先看一下map的用法
//语法:array.map(function(currentValue,index,arr), thisValue) //用法: var arr = [1,2,3,4]; arr.map((item,index,arr) => {return item*10 //新数组为10,20,30,40 }) //map遍历数组,返回一个新数组,不改变原数组的值。
用js实现
Array.prototype.fakeMap = function(fn,context) {let arr = this;let temp = [];for(let i=0;i<arr.length;i++){let result = fn.call(context,arr[i],i,arr);temp.push(result);}return temp; }
二.ES6面试
ES6新增方法:
1.let,const,var比较
2.反引号
3.函数默认参数
4.箭头函数
5.属性简写
6.方法简写
7.Object.keys方法,获取对象的所有属性名和方法名
8.Object.assign()原对象的属性和方法都合并到了目标对象
9.for...of循环
10.import和export
11.Promise
12.结构赋值
13.Set数据结构
14.Spread Operator展开运算符
15.字符串新增方法
ES6数组面试题:
1.forEach()
2.map()
3.filter()
4.reduce()
5.some()
6.every()
7.all()方法
ES6编程题:
1.使用解构,实现两个变量值的交换
let a = 1;
let b = 2;
[a,b] = [b,a];
2.利用数组推导,计算出数组
var arr1 = [1, 2, 3, 4];
var arr2 = [for (i of arr1) i * i];
console.log(arr2);
3.使用es6改下面的模板
let iam = "我是"; let name = "王德发"; let str = "大家好,"+iam+name+",多指教。";
改:
let iam = `我是`; let name = `王德发`; let str = `大家好,${iam+name},多指教。`;
4.把下面代码使用两种方法,来依次输出0到9?
var funcs = []for (var i = 0; i < 10; i++) {funcs.push(function() { console.log(i) })}funcs.forEach(function(func) {func()})
答:分别使用es5的闭包和es6的let
// ES5告诉我们可以利用闭包解决这个问题var funcs = []for (var i = 0; i < 10; i++) {func.push((function(value) {return function() {console.log(value)}}(i)))}// es6for (let i = 0; i < 10; i++) {func.push(function() {console.log(i)})}
5.http1.0,http1.1,http2.0,http3.0的区别?
http1.0
- 默认使用短连接--无状态,无连接
- 每个请求都需要新建TCP连接,性能较低
- 不支持多路复用
- 基于文本的协议
- 不支持头部压缩
- 请求头不支持Host头域
- 不支持服务器端推送
- 不支持请求优先级
- 不允许断点续传
- 默认不加密,可使用https加密
http1.1
- 默认使用长连接
- 允许在一个TCP连接上发送多个请求和响应,仍旧要求请求顺序发送和接收,即存在对头阻塞问题,这意味着一个请求的延迟可能会阻塞后续的请求,影响整体加载速度
- 基于文本协议
- 请求头支持Host头域
- 不支持头部压缩
- 增加更多的请求头和响应头来改进和扩充HTTP1.0的功能,比如身份证,状态管理和cache缓存等
- 提供了丰富的缓存控制机制,比如cache-control,ETag等
- 不支持服务器推送
- 不支持请求优先级
- 支持断点续传
- 默认不加密,可使用HTTPS加密
http2.0
- 采用二进制格式: 实现方便且健壮,与基于文本的http1.x协议不同
- 对头部进行高效压缩
- 一个tcp连接能够处理多个http请求,实现了多路连接共享,多个请求和响应可以在一个TCP连接上交错发送,解决了队头阻塞问题
- 依然基于TCP协议,受其拥塞控制和重传机制影响,可能存在延迟和性能瓶颈
- 支持服务端推送,服务端可主动向客户端发送消息
- 允许指定请求优先级
- 支持流量控制
http3.0
不依赖TCP,基于QUIC协议,不用TCP作为传输层协议,使用基于UDP的QUIC协议,QUIC继承了TLS加密,流量控制,多路复用等功能,并在用户空间实现了快速连接建立,前向纠错,更精细的拥塞控制等特性
降低了网络延迟
解决了HTTP2.0中前一个stream丢包导致后一个stream被拥塞的问题
不再用tcp四元组确定一个连接,而是使用一个64位随机数来确定一个连接
支持头部压缩,
更强的抗丢包能力
支持服务端推送: 服务端可主动向客户端发送消息
允许指定请求优先级
采用TLS1.3作为默认的安全层协议,提供更强的安全性
6.跨域的option是什么?
概念:
OPTIONS请求指的是Method为OPTIONS的http请求
作用:
它的作用是用于web服务器是否支持某些header,也可以叫做预检查请求(顾名思义:提前检测)
MDN定义:
HTTP的OPTIONS方法用于获取目的资源所支持的通信选项
OPTIONS请求的幅面影响:
- 请求数据会多一次往返的时间消耗,具体消耗多长时间,取决与用户到服务器(端到端)的网络延迟,及服务的响应时长。
通常在 50ms~500ms 之间。- 后端程序员或一些 nginx 规则经常不处理
OPTIONS 请求
,导致请求返回 404,后续请求失败。浏览器发起OPTIONS请求的条件:
- 请求跨域,且 2、3 满足一个
- 请求方法不是:HEAD、GET、POST
- HTTP 的头信息超出以下几种字段:
AcceptAccept-LanguageContent-LanguageLast-Event-ID Content-Type:只限于三个值: application/x-www-form-urlencoded、 multipart/form-data、 text/plain
大部分前后端程序员碰到OPTIONS请求是浏览器自动发起的
为什么只有跨域请求浏览器才会自动发起OPTIONS请求?
可以理解为,浏览器默认不同域名的服务器不是你维护的,保险起见需要先检查一下目标域名的服务器是否支持你自定义的header(或其他)
如何避免OPTIONS
- 配置网关转发规则,避免跨域
- 将请求转换简单请求,比如自定义的header提供query参数传递
- 如果你不需要读取该请求的返回内容,可设置mode:'no-cors'
mode: no-cors---保证请求对应的Method只有HEAD,GET或者POST方法,请求请求的headers只能有简单请求头
mode: 'no-cors'会忽略自定义的header
如果OPTIONS请求无法避免,设置缓存尽量降低其影响,比如缓存一年:
Access-Control-Max-age: 31536000,这样后续相同url的请求就不会发起OPTIONS请求了
注意:
缓存是针对URL的,URL一旦发生缓存就会失效(比如URL中有时间戳),会重复发起OPTIONS请求
7.协商缓存和强制缓存
背景介绍
浏览器和服务器进行交互的过程, 时间开销的瓶颈往往出现在数据的传输的过程之中。这个场景类似介于 A城 到 B城 之间只有一座 “通道” , 每次想从A城 到 B城 ,必须按照人数交付高昂的路费.
- 如果要减少这种高昂的路费开销的话, 核心思想就是尽可能的减少通过这座 “通道” 的次数又或者减少通过这座通道的“人数”。
- 基于这种理念,在 http协议的基础上, 提出了一种协议缓存
- 这种协议缓存又可以细分为 强制缓存 和 协商缓存 两种,分别对应上述减少过桥次数和减少过桥人数的理念。
http缓存机制简介
1) 强制缓存
- 强制缓存的思想是,在浏览器内置数据库中缓存每次请求中 “可以被缓存”的静态资源如 image, css, js 文件等
- 当第二次请求被缓存过的资源时候,会通过校验两个字段 Expires 和 Cache-Control 的max-age字段(注意,Expires 是 http1.0 的产物, Cache-Control 则是 http1.1 的产物)。
- Expires /Cache-Control 两者同时存在, 或者只存在其中之一, 都可以触发强制缓存
- 当满足字段约束的情况下, 浏览器就不会向服务器发送请求而是直接从服务器返回数据, 同时其状态码为 200
- 当不满足字段约束的情况下, 浏览器则会向服务器正常发送请求.
Expires 字段会存在失效的可能性?
- 当两个字段同时存在得到时候, Cache-Control 中的 max-age 字段字段优先级会稍微高一点, 当 Cache-Control 中的 max-age 字段校验成功,会直接返回浏览器内置数据库的缓存, 失效时才会将决策权传递给 Expires 字段判断。
- 这样设计的原因,大概是因为 Expires 字段在设计时存在了这么一个缺陷——Expires字段返回是服务器的时间, 而非客户端的本机时间。
- 当存在时差, 或者客户修改本地时间的情况下 Expires 字段会存在失效的可能性,比如 当同一时刻下的服务器时间为 2022/4/26 06:00:00 客户端时间为 2022/4/26 12:00:00 过期时间为两个小时之后, 则服务器会返回 2022/4/26 08:00:00 这个时间对应的值。
- 由于浏览器运行在客户环境下,对于客户而言, 这个缓存已经过期了,虽然缓存确实有效, 但是对于浏览器而言这个缓存确确实实是 “过期了”, 这会导致强制缓存永远不会生效!
- 为了解决这个问题, http 1.1 协议中添加了 Cache-Control 中的 max-age, 他是一个相对值, 即客户端获取到这个文件多少秒后失效, 其判别权力全权交由浏览器, 这会相对更准确些。
2) 协商缓存
- 协商缓存主要由 ETag 和 Last-Modified 两个字段来实现
- ETag 是一个用于映射 web 资源的映射 token,这个 token 应该满足唯一对应到一 个web服务器上的静态资源(具体实现通常是提取文件相关信息进行hash和base64编码等操作)
- Last-Modified 则通常是文件最后更新的日期时间戳
- (通过上述两个字段就可以判断当前文件是否是最新的数据)
流程:
- 浏览器首次向服务器请求数据 A, 服务器正常返回数据,同时在响应头中放入 ETag 和 Last-Modified 两个新字段。
- 当浏览器第二次向服务器请求数据 A 时, 浏览器会自动地在请求头附上 If-None-Match 和 If-Modified-Since 两个字段(分别对应的是 ETag 和 Last-Modified 的值,两两相等), 然后由服务器端进行校验, 校验通过的话(表明数据有效), 服务器会直接返回 状态码 304 ,且不携带响应体的报文段, 这相当于告诉浏览器:当前缓存有效, 可以直接使用! 校验失败则会和首次请求一样, 返回状态码为200且携带数据响应体的报文段, 同时这个响应头会带上新的ETag 和Last-Modified, 为下一次协商缓存做好铺垫 。
注意:
在不用框架的情况下, 协商缓存需要由后端开发人员手动实现,因此 ETag 和 Last-Modified 两个字段的优先级取决于开发者, 但是 Last-Modified 这个字段可以记录的时间戳精确度是有一定限制的,如果连续多次数据更新在精确度范围外, 会产生精确度丢失, 因此通常会让ETag 的优先级高于 Last-Modified 字段(类似于Cache-control中max-age一样, 属于是后续改进协议的一个新字段, 因此优先级一般会高点)
3) 强制缓存 + 协商缓存
强制缓存和协商缓存联合:
默认情况下, 浏览器会优先考量强制缓存的情况, 当强制缓存生效的情况下, 请求并不会到达服务器, 因此也就不会触发协商缓存。 当强制缓存失效的时候, 浏览器便会将请求传递到服务器, 于是服务器又会开始校验 If-Modified-Since 和 If-None-math 两个字段, 重复上述协商缓存的一个执行流程
乍一看,两者并存的情况, 有点像是两个协议的简单叠加,此时的协商缓存更像是强制缓存的兜底策略, 很可能协商缓存很长一段时间都不会生效(强制缓存过期时间设置过长的情况下), 因为强制缓存的优先级是要高于协商缓存的。 当然这并不是我们想看到的, 比方说当后端数据确实变更了, 而此时的浏览器由于使用了强制缓存,则会出现数据不一致的情况, 因此在这里引入了请求头中的两个字段 no-cache, 当使用了 no-cache 字段的时候, 浏览器将不再使用强制缓存, 而是直接去请求服务器, 这个时候就会用到协商缓存了(顺带一提的是, 还有一个 no-store 字段, 用了这个字段浏览器则不会在使用缓存的数据也不缓存数据,即强制缓存和协商缓存都失效了)
缓存机制之间的一些区别:
- 强制缓存在缓存有效的情况下不会去请求服务器, 其数据来源则是浏览缓存的本地磁盘。
- 而协商缓存会向服务器请求,但是在协商缓存成功的情况下, 服务器只会返回一个不带响应体的报文,结合开头的背景来说 强制缓存选择“减少过桥次数”的策略, 而协商缓存则是采用 ‘减少过桥人数’的策略
- 强制缓存在浏览器强制刷新的情况下不会生效, 而协商缓存则不受影响。(调试代码测试时候,要注意)
- 强制缓存返回的报文状态码为 200, 协商缓存返回的报文状态码为 304 (前端使用fetch请求的情况, 协商缓存的 状态码304 会转成 200)
- 强制缓存发生在浏览器端, 协商缓存发生在服务器端
小结:
- 强制缓存存在一个瓶颈, 当浏览器用户强刷新时,浏览器会直接跳过强制缓存, 这点不注意很容易会被忽视掉。
- 强制缓存不适合 SPA 应用的入口文件, 因为重新部署后, 用户如果没有强制刷新, 则无法在第一时间内看到新的网页内容。
- 作为一个前端开发者可以通过设置请求头中的 no-cache 和 no-store 字段选择使用协商缓存或者不使用缓存!!!
8.js正则-断言
?=
- 零宽先行断言
- 省流: 后面是什么,理解为正则表达式的条件语句
- 真正的内容在(?=xxx)的左边
eg:
百度,后边是地址的加上链接
let str='百度地址链接在左边'; let reg=/百度(?=地址链接在左边)/g; let result; console.log(result=str.replace(reg,'<a href="http://www.baidu.com">$&</a>'));
(?=xx)只是条件,不会放在组中
常见的千分位面试题:
var str='1111000.12' var pattern = new RegExp(`\\B(?=(\\d{3})+(?!\\d))`, 'g') var str=str.replace(pattern,'$&,') console.log(str)
?<=
- 零宽后行断言
- 省流: 前面是什么,用来判断前面的条件
- 真正的内容在(?<=xxx)的右边
注意: 断言,匹配结果是看不到的,不要把它当成组
eg:
把网址全部换成www.baidu.com
var str=`<a href="https://www.a.com">百度1</a><a href="https://www.b.com">百度2</a> `; /** 正则表达式中的反向引用概念: 在正则表达式中,\1(在某些正则表达式引擎中也可能是$1)是一种反向引用。它用于引用前面捕获组匹配到的内容。 捕获组: 在这个正则表达式/(?<=href=(['"])).+(?=\1)/gi中,(?<=href=(['"]))是一个正向零宽度断言,它匹配href=后面跟着一个单引号或者双引号的位置。这里的(['"])是一个捕获组,它会捕获这个单引号或者双引号。 \1在这个表达式中是引用前面捕获组(也就是(['"]))捕获到的单引号或者双引号。这样,+(?=\1)部分就表示匹配一个或多个字符,直到遇到前面捕获组所捕获的那个引号为止。 */ var reg=/(?<=href=(['"])).+(?=\1)/gi; console.log(str.match(reg)); // replace的部分是(?<=href=(['"]))右边,(?=\1)右边的内容,即href="内容"中去掉左边的href=",再去掉右边的",只替换内容 console.log(str.replace(reg,'https://www.badiu.com'));
?!
- 零宽负向先行断言
- 省流: 理解为后面不是什么
使用场景和示例
匹配不以特定字符开头的字符串:
例如,要匹配不以
a
开头的字符串,可以使用[^a]
。匹配不以特定单词结尾的字符串:
例如,要匹配不以
end
结尾的字符串,可以使用(?!end)
。eg: 不能出现哈喽:
(?!.*哈喽)
是一个前瞻否定断言,它确保在当前位置之后的字符串中不存在 “哈喽” 这个词。- 这里的
.*
表示匹配任意数量的任何字符,所以这个断言的意思是在整个字符串中,从当前位置开始不能有任何字符序列最终导致出现 “哈喽”。- 如果返回true,表示不包含哈喽
- 如果返回false,就表示包含哈喽
var str = "这是一段文本,没有哈喽。"; var regex = /^(?!.*哈喽).*$/; console.log(regex.test(str));
?<!
- 零宽负向后行断言
- 省流:前面不是什么
- 作为前面正则的附加条件
- 内容为(?<!xxx)左边+(?<!xxx)右边,(?=xxx)左边(即改为类似https://www.xx.com的格式)
- 与?=相反
eg:
// 不包含www:
(?<!www)
// 可以包含斜杠/
(?=\/)
真正需要替换的内容部分是(?<!www)右边,(?=\/)左边的,即中间\..+匹配的字符串进行replace
var str=`<a href="https://www.a.com/xx.jpg">xx.jpg</a><a href="https://oss.a.com/xx.jpg">xx.jpg</a><a href="https://cdn.a.com/xx.jpg">xx.jpg</a><a href="https://a.com/xx.jpg">baidu</a> ` var reg=/(?<=href="https:\/\/).*(?=a.com)/gm console.log(str.match(reg)); console.log(str.replace(reg,'www.')); // 或者 reg=/https:\/\/([a-z]+)?(?<!www)\..+?(?=\/)/g console.log(str.match(reg)); console.log(str.replace(reg,'https://www.badu.com'));
9.格式化网址的query字符串
eg1:
// 格式化query字符串(要考虑到同名key的情况,
// 例:?a=1&a=2&b=3 最终结果为 { a: [1, 2], b: 3 })
// 格式化query字符串(要考虑到同名key的情况,
// 例:?a=1&a=2&b=3 最终结果为 { a: [1, 2], b: 3 })
function parseQueryString(queryString) {var str = queryStringvar idx1 = str.indexOf('?')str1 = idx1 >= 0 ? str.substring(1) : strvar query = {}// 先组成一个新的对象数组,格式为 [{key: 'a', value: '1'}, {key: 'a', value: '2'}, {key: 'b', value: '3'}]var arr = str1.split('&').map((item, index) => {var idx = item.indexOf('=')// 获取key值var key = item.substring(0, idx)// 获取value值var value = item.substring(idx + 1)return {key: key,value: value,}})// 遍历对象数组,筛选key值一致的分组,并组成新的对象数组// 如果key值筛选出来只有一项,则值为字符串// 如果key值筛选出来由多项相同,则值为数组// 给query对象赋值// 最终预期结果为: { a: [1, 2], b: 3 }arr.forEach((item) => {var arr0 = arr.filter(ee => ee.key === item.key)query[item.key] = arr0.length > 1? arr0.map(ee => ee.value): item.value});return query
}var str = '?a=1&a=2&b=3'
console.log(parseQueryString(str))
eg2:
// 格式化query字符串
// 例:?a=1&b=2&c=3 最终结果为 { a:1, b: 2,c=3 })
// 格式化query字符串
// 例:?a=1&a=2&b=3 最终结果为 { a: 1, b: 2, c:3 })
function parseQueryString(queryString) {var str = queryStringvar idx1 = str.indexOf('?')str1 = idx1 >= 0 ? str.substring(1) : strvar query = {}str1.split('&').forEach((item) => {var idx = item.indexOf('=')// 获取key值var key = item.substring(0, idx)// 获取value值var value = item.substring(idx + 1)query[key] = value})return query
}var str = '?a=1&b=2&c=3'
console.log(parseQueryString(str))
10.基本数据类型和引用类型地址是栈还是堆?
基本数据类型:
- (如
number
,string
,boolean
,null
,undefined
和symbol
)的值直接存储在栈内存中。引用类型:
- (如
object
,array
,function
)的值存储在堆内存中,但在栈内存中会存储一个指向堆内存中实际数据的引用。地址的区别:
栈内存中的地址:
- 存储的是基本数据类型的值。
- 存储的是引用类型的引用(指针)。
- 栈内存中的地址是固定的,由编译器管理。
堆内存中的地址:
- 存储的是引用类型的实际数据。
- 堆内存中的地址是动态分配的,由垃圾回收机制管理。
11. 前端工程化
什么是前端工程化?
指的是将前端开发过程中的一系列流程和工具进行规范和自动化,从而提高开发效率,减少重复劳动,降低出错率。前端工程化的目标是让前端开发更高效
1) 定义:
将开发的流程,工具和规范化,并使用相关技术实现自动化,包括代码编写,测试,构建,部署等环节,以提高前端开发效率,提高代码质量和可维护性
2) 为什么需要前端工程化:
- 提高开发效率: 提高代码质量和可维护性,减少出错和重复工作
- 减轻开发人员的工作负担,专注业务逻辑的开发
核心概念:
- 模块化
- 打包构建
- 自动化部署
- 自动化测试
- 持续集成
模块化:
- 将一个大的应用程序分成多个小的模块,每个模块都有自己的功能和特点,可以独立开发,测试和维护。
- 常见的模块化方案: CommonJS,ES6模块,AMD等
打包构建:
- 多个模块组合起来,生成可以在浏览器中运行的代码
- 过程: 压缩,文件合并,资源管理等
- 常见的打包工具: webpack, rollup等
自动化部署:
- 将打包后的代码部署到生产环节或者测试/开发环节中的自动化过程
- 自动化部署可以减少手动部署的错误和工作量,同时也可以缩短部署的时间
- 自动化部署工具: Jenkins, Drone, GitLab CICD
自动化测试:
- 使用自动化工具对代码进行测试
- 确保在开发中不会出现问题,部署到生产环境也不会出现问题
- 分为两类: 单元测试和端对端测试(单元测试指的是应用程序最小的可测试单元,比如一个函数或类; 端对端测试指的是应用程序的整个流程,包括用户界面和后端逻辑)
- 优势: 提高开发效率和代码质量
持续集成:
- 开发过程中,频繁将代码集成到共享代码库中,每次集成都会自动化构建和自动化测试
- 确保代码稳定性和质量,更快速地检测和修复错误
- 优势:提高开发效率,加速代码部署,减少错误,加强团队协作性,更高效地响应客户需求
工程化主要工具:
- 包管理: npm, yarn
- 构建: webpack, rollup, Parcel, Gulp, Grunt
- 自动化测试: Jest, Mocha, Karma, Cpress, Puppeteer
- 集成: Jenkins, GitLab CI/CD, Github Actions
常见的工程化的应用方案:
- 模块化: CommonJS, AMD,ES6模块等
- 打包构建:webpack, rollup等
- 自动化部署: Jenkins等
- 自动化测试: Jest等
- 持续集成: Jenkins等
打包构建优化:
- 使用webpack将多个模块打包,对代码进行压缩,代码分割,异步加载,懒加载,cdn配置等减少页面加载时间和提高性能
- 使用Jest实现自动化测试,即使发现和处理问题
- Jenkins实现自动化部署,自动构建并部署代码到服务器
学习工程化:
- 掌握基本前端技术
- 学习相关的工具和框架
- 多做实战
- 学点运维知识