概述
js面试题
- 1、js的数据类型
- 2、原型,原型链
- 3、闭包
- 垃圾回收
- 4、this指向
- apply、call、bind
- new一个对象的过程
- var、let、const
- 5、DOM事件
- 事件绑定的方式
- DOM0,DOM2,DOM3事件处理方式区别
- DOM事件流
- 事件委托(事件代理)
- IE事件处理和W3C的事件处理区别
- 6、javascript的运行机制
- 一、基础
- 二、js中的异步操作
- 三、同步任务、异步任务
- 四、宏任务、微任务
- 五、Even Loop事件循环
- 7、浏览器的渲染过程,DOM树和渲染书的区别?
- 8、如何实现继承
- 防抖节流
- 深拷贝、浅拷贝
- 函数柯里化
- 9、 cookie和storage
- promise和async await
- 内存泄露
- xss和csrf攻击
- ajax和fetch
1、js的数据类型
- 基本数据类型:undefined、null、boolean、number、string、symbol(ES6新增)
- 引用数据类型:object、array、function
基本数据类型: - 占用空间固定,保存在
栈
中:当一个方法执行时,每个方法都会建立自己的内存栈,也就是所谓的函数作用域,基础变量的值是存储在栈中的,而引用类型变量存储在栈中的是指向堆中的数组或对象的“地址” - 保存与复制的是值的本身
- 可以用typeof检测值类型
引用类型 - 占用空间不固定,保存在
堆
中:在程序中创建一个对象时,这个对象将被保存到运行时数据区(堆内存)中,以便反复利用 - 保存与复制的是指向对象的一个指针
- 使用instanceof() 检测数据类型,返回布尔值,左边是引用类型,右边是函数
- 使用new()方法构造出的对象是引用型
JS有哪些内置对象? - 数据封装类对象:Object、Array、Boolean、Number、String
- 其他数据对象:Function、Arguments、Math、Date、RegExp、Error
- ES6新增对象:Symbol、Map、Set、Promise、Proxy、Reflect
2、原型,原型链
-
原型:
- javascript的所有引用类型(数组、对象、函数) 中都包含一个_proto_
内部属性,这个属性所对应的就是该对象的原型
- JavaScript的所有函数都有一个prototype属性
- 当函数对象作为构造函数创建实例时,该prototype属性值将被作为实例对象的原型_proto_
- prototype用于访问函数的原型对象
- _proto_用于访问对象实例的原型对象function Test() {}
const test = new Test()
test._ proto_ == Test.prototype
他们都用来访问原型对象的
函数不仅是个函数,还是个对象,所以他也有_proto_属性,因为函数时内置构造函数Function的实例
Test.prototype._ proto_ == Object.prototype
Object.prototype._ proto_ == null -
原型链:
- 当一个对象调用的属性/方法自身不存在时,就会去自己_proto_
关联的前辈prototype对象上去找(找对象的原型)
- 如果没找到,就会去该prototype原型关联的前辈prototype去找。以此类推,直到找到属性/方法或undefined为止。从而形成所谓的“原型链”
- -
原型特点:
- JavaScript对象是通过引用来传递的,当修改原型时,与之相关的对象也会继承这一改变
3、闭包
闭包就是指有权访问另一个函数作用域中的变量的函数。函数A内有一个函数B,函数B可以访问到函数A中的变量,那么函数B就是闭包。
通常,函数的作用域及其所有变量都会在函数执行结束后被摧毁。但是,在创建了一个闭包后,这个函数的作用域就会一直保存到闭包不存在为止
closure就是一个闭包
function func(){
var a=1,b=2;
function closure(){
return a+b;
}
return closure;
}
闭包的应用:设计私有的方法和变量,封装变量
在JavaScript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;如果两个对象互相引用,而不再被第三者所引用,那么这两个互相引用的对象也会被回收。
闭包的缺点
闭包的缺点就是常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露。
局部变量本来应该在函数退出的时候被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去,从这个意义上看,闭包的确会使一些数据无法及时销毁。使用闭包的一部分原因是我们主动把一些数据封闭在闭包中,因为可能在以后还需要使用者些变量 ,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的,这里并不能说成是内存泄露。如果在将来需要回收这些变量,我们可以手动把这些变量设为null。
跟闭包和内存泄露有关的是,使用闭包的同时比较容易形成循环引用,如果闭包的作用域中保存着一些DOM节点,这个时候就可能造成内存泄露。因为在IE浏览器中BOM和DOM中的对象是使用C++以COM对象的方式实现的,而COM对象的垃圾收集机制采用的是引用计数策略。在基于引用计数策略的垃圾回收机制中,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收,但循环引用造成的内存泄露在本质上也不是闭包造成的。
垃圾回收
- 标记清除
1、这是JavaScript最常用的垃圾回收方式,当变量进入执行环境时,比如函数声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境时(函数执行结束)将其标记为“离开环境”
2、垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量
- 引用计数
在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另一个,则这个值的引用次数减1,当这个值的引用次数变成0的时候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
4、this指向
- this总是指向函数的直接调用者
- 如果由new关键字,this指向new出来的实例对象
- 在事件中,this指向触发这个事件的对象
- 默认:严格模式下绑定到undefined,否则绑定到全局对象window
- 匿名函数的this永远指向window
- 箭头函数没有自己的this指针,他的this绑定取决于父级(函数或全局)作用域,任何方法都改变不了
- call、apply、bind绑定到指定的对象
改变this指向:使用ES6的箭头函数在函数内部使用_this=this使用apply 、call 、bind new实例化一个对象
apply、call、bind
都继承自Function.prototype中的,属于实例方法(改变函数内this指向)
- call():可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数,并且会立即执行该函数。
call()函数可以传递两个参数,第一个参数是指定函数内部this的指向,第二个参数是函数调用时需要传递的参数,一个一个传。 - apply():与call()方法类似,区别是call方法传参时参数是一个一个传的,而apply是以一个数组的方式来传,都是在调用时生效,改变调用者的this指向
b.apply(a,[1,2])
b.call(a,1,2)
-
bind():用于指定函数内部的this指向,然后返回一个新函数,bind方法并非立即执行一个函数
联系与区别: -
第一个参数都是指定函数内部this的指向,然后根据指定的作用域调用该函数
-
都可以在函数调用时传递参数,call、bind方法需要直接传入,而apply方法需要以数组的形式传入
-
call、apply方法是在调用之后立即执行函数,而bind方法没有立即执行,需要将函数再执行一遍。
new一个对象的过程
function Test(){}
const test = new Test()
1、创建一个空的对象
const obj = {}
2、设置新对象的constructor属性为构造函数的名称,设置新对象的_proto_属性指向构造函数的prototype对象(将新创建的空对象的隐式原型指向其构造函数的显示原型)
obj.constructor = Test
obj._proto_ = Test.prototype
3、使用新对象调用函数,函数中的this被指向新实例对象(使用call改变this的指向)
Test.call(obj)
4、将初始化完毕的新对象地址,保存到等号左边的变量中
var、let、const
- let所声明的变量只在let命令所在的代码块内有效(
块级作用域
) - let命令
不存在变量提升
。变量在声明语句之后才能使用 - let声明变量
存在暂时性死区
(即变量会绑定某个区域,不受外部影响)ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错 - let命令
不允许重复定义
但var可以而且后定义的变量会覆盖掉前面的变量 - const声明一个只读的
常量
。一旦声明,常量的值就不能改变。其他性质与let一样 - var的作用域是
函数作用域
,存在变量声明提前
5、DOM事件
事件绑定的方式
- 嵌入DOM
<button onclick="func()">按钮</button>
- 直接绑定
btn.onclick = function(){}
- 事件监听
btn.addEventListener('click',function(){})
DOM0,DOM2,DOM3事件处理方式区别
- DOM0级事件处理方式:
- btn.onclick = func;
- btn.onclick = null; - DOM2级事件处理方式:
- btn.addEventListener(‘click’,func,false);
- btn.removeEventListener(‘click’,func,false);
- btn.attachEvent(“onclick”,func);
- btn.detachEvent(“onclick”,func);
- 可以冒泡和捕获 - DOM3级事件处理方式:
- eventUtil.addListener(input,“textInput”,func);
- eventUtil是自定义对象,textInput是DOM3级事件
DOM事件流
事件的三个阶段:捕获、目标、冒泡
事件捕获阶段:时间从祖先元素往子元素查找(DOM树),直到捕获到事件目标target。这个过程中,默认情况下,事件相应的监听函数是不会被触发的。在事件捕获阶段时间流模型:window->document->html->body->div(父元素、子元素、目标元素)
事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数,如果没有绑定监听函数,那就不执行。
事件冒泡阶段:事件从事件目标target开始,从子元素往冒泡祖先冒泡,直到页面的最上一级标签。div->body->html->document->window。冒泡指的是子元素的事件被触发,父元素同样的事件也会被触发
DOM时间流:首先发生的是事件捕获,为截获事件提供了机会,然后是实际的目标接收到事件,最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应
可以选择在捕获阶段还是冒泡阶段绑定事件处理函数,通过addEventListener()实现的,如果函数的最后一个参数是true,则是在捕获阶段绑定函数,反之,在冒泡阶段绑定函数
window.addEventListener('click',function() {
alert("捕获window");
}, true);
在JS中,如果想获取HTML节点,方法是:document.documentElement;获取body节点,方法是:document.body
document.onclick = function(){
alert("冒泡body");
}
不是所有的事件都能冒泡:blur、focus、load、unload、ommouseenter、onmouseleave。事件不会往父元素那里传递
阻止冒泡
w3c的方法(火狐、谷歌、IE11):event.stopPropagation();
IE10以下:event.cancelBubble=true
事件委托(事件代理)
将事件委托给另外的元素,利用了事件冒泡原理,将事件绑定到目标元素的父节点,只指定一个事件处理程序,就可以管理某一类型的所有事件。所有用到按钮的事件(所属鼠标和键盘事件)都适合采用事件委托技术,使用事件委托可以节省内存。
IE事件处理和W3C的事件处理区别
-
绑定事件
- w3c:targetEl.addEventListener(‘click’,handler,false);
- IE:targetEl.attachEvent(‘onclick’,handle); -
删除事件
-w3c:tragetEl.removeEventListener(‘click’,handle,false);
IE:targetEl.detachEvent(event,handle) -
事件对象
-w3c:var e = arguments.callee.caller.argument[0]
IE:window.event -
事件目标
-w3c:e.target
IE:window.event -
阻止事件默认行为
-w3c:e.preventDefault()
IE:window.event.returnValue = false -
阻止事件传播
-w3c:e.stopPropagation()
IE:window.event.cancelBubble = truew3c事件的target与currentTarget的区别?
-
event.target 返回触发事件的元素
-
event.currentTarget 返回绑定事件的元素
-
target只出现在事件流 的目标阶段,currentTarget可能出现在事件流的任何阶段
-
当事件流处在目标阶段时,二者的指向相同
-
当事件流处于捕获或冒泡阶段时,currentTarget指向当前事件活动的对象
6、javascript的运行机制
一、基础
- js作为浏览器脚本语言,主要用途是与用户互动,以及操作DOM,因此
js是单线程
- H5提出web-worker但js是单线程这一核心为改变,所有一切JavaScript版的“多线程”都是用单线程模拟出来的,并且不允许操作DOM
- js引擎存在monitoring process进程,会持续不断的检查主线执行栈是否为空,一旦为空,就会去Even Queue哪里检查是否有等待被调用的函数。这个过程是循环不断的,
所以整个运行机制又称为Even Loop(事件循环)
- 所有同步任务都是在主线程上指向,形成一个执行栈
二、js中的异步操作
- setTimeOut
- setInterval
- ajax
- promise
- I/O
三、同步任务、异步任务
- 同步:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
- 异步:不进入主线程,而进入“任务队列”的任务,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
四、宏任务、微任务
- 宏任务:整体代码script、setTimeout、setInterval,ajax
- 微任务:promise.then、promise.nextTick(node),async,await
- 注意:new promise是会进入到主线程中立即执行,而promise.then属于微任务
先执行宏任务,再执行微任务
五、Even Loop事件循环
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');
执行结果
//promise、console、then、setTimeout
}
分析:
1.这段代码作为宏任务,进入主线程。
2.先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue。
3.接下来遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue。
3.遇到console.log(),立即执行。
4.整体代码script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行。
5.ok,第一轮事件循环结束了,我们开始第二轮循环,当然要从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行。
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
执行结果
//1,7,6,8,2,4,3,5,9,11,10,12
分析:
1.整体script作为第一个宏任务进入主线程,遇到console.log,输出1;
2.遇到setTimeout,其回调函数被分到宏任务Event Queue中,我们记为setTimeout1;
3.遇到process.nextTick(),其回调函数被分发到微任务Event Queue中,我们记为process1;
4.遇到promise,new promise直接执行,输出7。then被分发到微任务Event Queue中,我们记为then1;
5.又遇到setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2;
6.第一次事件循环宏任务结束,我们发现process1和then1两个微任务,输出6和8;
7.第一次事件循环结束,此时输出1,7,6,8。第二轮事件循环从setTimeout1宏任务开始;
8.首先输出2,接下来遇到process.nextTick(),同样将其分发到微任务Event Queue中,记为process2。new promise立即执行输出4,then也分发到微任务Event Queue中,记为then2;
9.第二轮事件循环宏任务结束,发现process2和then2两个微任务,输出3和5;
10.第二轮事件循环结束,输出2,4,3,5;
11.第三轮事件循环开始,此时只剩setTimeout2,输出9;
12.将process.nextTick()分发到微任务Event Queue中,记为process3;
13.直接执行new Promise,输出11;
14.将then分发到微任务Event Queue中,记为then3;
15.第三轮事件循环宏任务结束,执行两个微任务process3和then3,输出10,12;
16.第三轮事件循环结束,输出9,11,10,12。
记住:
- JavaScript是一门单线程语言
- Event Loop是JavaScript的执行机制
7、浏览器的渲染过程,DOM树和渲染书的区别?
-
浏览器的渲染过程:
- 解析HTML构建DOM(DOM树),并行请求css/image/js
- CSS文件下载完成,开始构建CSSOM(CSS树)
- CSSOM构建结束后。和DOM一起生成Render Tree(渲染树) -
DOM树和渲染树的区别:
- DOM树与HTML标签一一对应,包括head和隐藏元素
- 渲染树不包括head和隐藏元素,大段文本的每一行都是独立节点,每一个节点都有对应的css属性
重绘和回流(重排)的区别和关系 -
重绘:当渲染树中的元素外观(颜色)发生改变,不影响布局时,产生重绘
-
回流:当渲染树中的元素的布局(尺寸、位置、隐藏/状态)发生改变时,产生重绘回流
-
注意:JS获取Layout属性值(offsetLeft、scrollTop等)也会引起回流,因为浏览器需要通过回流计算最新值
-
回流必将引起重绘,而重绘不一定会引起回流
8、如何实现继承
ES只支持实现继承,其继承主要依靠原型链来实现。
实现继承首先需要一个类,在js中实际上是没有类的概念,在es6中class只是es5的语法糖
function People(name){
//属性
this.name = name || "Anna";
//实例方法
this.sleep = fuction(){
console.log(this.name + '正在睡觉')
}
}
//原型方法
People.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
}
- 1、原型链继承:父类的实例作为子类的原型
function Woman(){
}
Woman.prototype = new People();
Woman.prototype.name = 'huahua';
let womanObj = new Woman();
优点:父类新增的原型方法与属性,子类也能访问
缺点:1、可以在子类中增加实例属性,如果要新增加原型属性和方法需要放在在new父类构造函数的后面
2、无法实现多继承
3、创建子类实例时,不能向父类构造函数中传参数
4、来自原型对象的所有属性被所有实例共享
- 2、构造函数绑定:使用call或apply方法,复制父类的实例属性给子类
function Woman(name){
People.call(this);//People.call(this, 'huahua');
this.name = name || 'renbo'
}
let womanObj = new Woman();
优点:1、创建子类实例时,可以向父类传递参数
2、可以实现多继承(call或者apply多个父类)
缺点:1、实例只是子类的实例
2、只能继承父类的实例属性和方法,不能继承原型属性和方法
3、方法都在构造函数中定义,无法复用
- 3、实例继承(原型式继承):为父类实例添加新特性,作为子类实例返回
function Woman(name){
let instance = new People();
instance.name = name || 'huahua';
return instance;
}
let womanObj = new Woman();
优点:不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果
缺点:1、实例是父类的实例
2、不支持多继承
- 组合式继承
调用父类构造函数,继承父类的属性,通过将父类实例作为子类原型,实现函数复用
function People(name,age){
this.name = name || 'huahua'
this.age = age || 27
}
People.prototype.eat = function(){
return this.name + this.age + 'eat sleep'
}
function Woman(name,age){
People.call(this,name,age)
}
Woman.prototype = new People();
Woman.prototype.constructor = Woman;
let womanObj = new Woman(ren,27);
womanObj.eat();
优点:1、可以继承实例属性/方法,也可以继承原型属性/方法
2、既是子类的实例,也是父类的实例
3、不存在引用属性共享问题
4、可传参
5、函数可复用
缺点:调用两次父类,产生两份实例
- 寄生组合继承
通过寄生的方式,砍掉父类的实例属性,在调用两次父类构造时,就不会初始化两次实例方法/属性,来修复组合式继承的不足,完美的实现继承
//父类
function People(name,age){
this.name = name || 'huahua'
this.age = age || 27;
}
//父类方法
People.prototype.eat = function(){
return this.name + this.age + 'eat sleep'
}
//子类
function Woman(name,age){
//继承父类属性
People.call(this,name,age)
}
//继承父类方法
(function() {
//创建空类
let Super = function(){};
Super.prototype = People.prototype;
//父类的实例作为子类的原型
Woman.prototype = new Super();
})();
//修复构造函数指向问题
Woman.prototype.constructor = Woman;
let womanObj = new Woman();
- ES6继承
//class 相当于es5中构造函数
//class中定义方法时,前后不能加function,全部定义在class的protopyte属性中
//class中定义的所有方法是不可枚举的
//class中只能定义方法,不能定义对象,变量等
//class和方法内默认都是严格模式
//es5中constructor为隐式属性
class People{
constructor(name='huahua',age='27'){
this.name = name;
this.age = age;
}
eat(){
console.log(`${this.name} ${this.age} eat food`)
}
}
//继承父类
class Woman extends People{
constructor(name='ren',age='27') {
super(name,age); // 调用父类的constructor(x, y)
}
eat() {
//继承父类方法
super.eat()
}
}
let womanObj = new Woman('huahua');
womanObj.eat();
防抖节流
防抖节流详细介绍
防抖:
按钮提交场景:防止多次提交按钮,只执行最后一次提交的一次;服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似搜索框输入查询,如果用户一直在输入中,没有必要不停地调用去请求服务端接口,等用户停止输入时再调用,设置一个合适的时间间隔,有效减轻服务端压力;浏览器窗口缩放,resize事件(如窗口停止改变大小后重新计算布局)
节流
拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动;缩放场景:监控浏览器resize;动画场景避免短时间内多次触发动画引起性能问题
区别: 防抖是将多次执行变成最后一次执行;节流是将多次执行变成每隔一段时间执行
深拷贝、浅拷贝
拷贝是直接拷贝变量的值,而引用类型拷贝的其实是变量的地址。
浅拷贝: 如果在拷贝这个对象的时候,只对基本数据类型进行拷贝,而对引用数据类型只是进行了引用的传递,而没有重新创建一个新的对象,则认为是浅拷贝。如果其中一个对象改变了这个地址,就会影响到另一个对象。
var obj1 = {name: 'obj1'}
var obj2 = Object.assign({},obj1)
var obj2 = {...obj1}
深拷贝: 在对引用数据类型进行拷贝的时候,将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象,则认为是深拷贝。
//1.JSON.stringify()/JSON.parse()
let obj = {a:1,b:{x:3}}
JSON.parse(JSON.stringify(obj))
//利用JSON.stringify将js对象序列化(JSON字符串),再使用JSON.parse来反序列化js对象;序列化的作用是存储和传输
//缺点
// 对象的属性值是函数时,无法拷贝。
// 原型链上的属性无法拷贝
// 不能正确的处理 Date 类型的数据
// 不能处理 RegExp、Error对象,得到空对象
// 会忽略 symbol
// 会忽略 undefined
2、递归拷贝
//如果是原始类型,无需继续拷贝,直接返回
//如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上。
function deepClon(obj){
let copy = obj instanceof Array ? [] : {}
for(let i in obj){
if(obj.hasOwnProperty(i)) {
copy[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
}
}
return copy
}
函数柯里化
柯里化(Currying)是函数式编程的一个重要概念,将使用多个参数的一个函数转换成一系列使用一个参数的函数
使用add函数,简单实现一下
//普通函数
function add(x,y) {
return x + y
}
//curring后
function curryingAdd(x) {
return function (y) {
return x + y;
}
}
add(1,2) //3
curryingAdd(1)(2) //3
实际上就是把add函数的x,y两个参数变成了先用一个函数接收x然后返回一个函数去处理y参数。
主要有三个作用:1.参数复用;2.提前返回;3.延迟计算/运行
- 参数复用
// 正常正则验证字符串 reg.test(txt)
// 函数封装后
function check(reg,txt) {
return reg.test(txt)
}
check(/d+/g, 'test') //false
check(/[a-z]+/g, 'test') //true
// Currying后
function curryingCheck(reg) {
return function(txt){
return reg.test(txt)
}
}
var hasNumber = curryingCheck(/d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)
hasNumber('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false
上面的示例是一个正则的校验,正常来说直接调用check函数就可以了,但是如果有很多地方都要校验是否有数字,其实就是需要将第一个参数reg进行复用,这样别的地方就能够直接调用hasNumber,hasLetter等函数,让参数能够复用,调用起来也更方便。
- 提前返回
// 比如:解决原生方法在现代浏览器和IE之间的兼容问题
// 提前返回: 使用函数立即调用进行了一次兼容判断(部分求值),返回兼容的事件绑定方法
// 延迟执行:返回新函数,在新函数调用兼容的事件方法。等待addEvent新函数调用,延迟执行
const addEvent = (function() {
if(window.addEventListener) {
return function(ele, type, fn, isCapture) {
ele.addEventListener(type, fn, isCapture)
}
} else if(window.attachEvent) {
return function(ele, type, fn) {
ele.attachEvent("on" + type, fn)
}
}
})()
- 延迟计算
//js中bind实现机制正是currying
Function.prototype.bind = function(context){
var _this = this;
var args = Array.prototype.slice.call(arguments,1)
return function() {
return _this.apply(context, args)
}
}
9、 cookie和storage
SessionStorage和LocalStorage、cookie这三者都可以被用来在浏览器端存储数据。而且都是字符串类型的键值对。区别在于前两者属于webStorage,创建他们的目的便于客户端存储数据。而cookie最初目的是为了保持HTTP的状态。
cookie是服务器发送到用户浏览器并保存在浏览器上的一块数据(用户、密码、浏览的网页等)他会在浏览器下一次发起请求时被携带并发送到服务器上。cookie属性有:Name、value、Domain、Path、Max-age、Secure、HTTPOnly表示不能够被js脚本访问
,因为js能够通过document.cookie来获取cookie
,所以使用HTTPOnly就能够阻止这种情况,在一定程度上防止XSS攻击
,也就是跨脚本攻击。
共同点: 都保存在浏览器端,且同源
区别:
1、保存地:cookie数据始终在同源的HTTP请求中携带,即cookie在浏览器和服务器间来回传递
,而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存
2、存储大小 : cookie数据不能超过4K,因为每次http请求都会携带cookie,所以只适合保存小的数据
。sessionStorage和localStorage虽也有大小限制,但比cookie要大的多,可以达到5M或更大
。
3、数据有限期:
- sessionStorage:仅在当前浏览器窗口关闭前有效。
- localStorage:始终有效,窗口或浏览器关闭也一直保存,除非主动删除。
- cookie:只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。
4、作用域不同:cookie和localStorage是在同源窗口中都是共享的。 sessionStorage不在不同的浏览器窗口共享,即使是同一页面。
promise和async await
Promise: 是一个对象,用来传递异步操作的消息,他代表了某个未来才会知道结果的事件(一个异步操作),是异步编程的一种解决方案。promise对象有两个特点:
(1)对象的状态不受外界影响。promise对象代表一个异步操作,有三种状态:pending(进行中),fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
(2)一旦状态改变,就不会再变。状态改变有两种可能:从pending变成resolved和从pending变成rejected。
promise用来解决:回调地狱、支持多个并发的请求、解决异步的问题。
缺点:1、无法取消promise,一旦建立就立即执行,无法中途取消
2、如果不设置回调函数,promise内部抛出的错误不会反应到外部
3、当处于pending状态时,无法得知目前进展到哪一个阶段。
promise有all、reject、resolve方法,原型上有then、catch方法。
resolve:异步操作执行成功后的回调函数。
reject:异步操作执行失败后的回调函数
then:传两个参数,第一对应resolve的回调,第二对应reject回调
catch:用来指定reject的回调,在执行resolve的回调时,如果抛出了异常,并不会报错卡死js,而是会进到catch方法中。
all:提供了并行执行异步操作的能力,并在所以异步操作执行完后才执行回调。
async await:1、是写异步代码的新方式,以前的方法有回调函数和promise
2、基于promise实现的。他不能用于普通的回调函数
3、与promise一样,是非阻塞的
4、使异步代码看起来像同步代码
假设函数getJSON返回值是Promise,并且Promise resolves有一些JSON对象,我们只想调用它并且记录该JSON并返回完成。
//使用promise
const request =() => {
getaJSON().then(data=>{
console.log(data)
retuen 'done'
})
}
request();
//使用Async
const request = async() => {
console.log(await getJSON())
return 'done'
}
request();
区别:1)函数前面多了一个async关键字,await关键字只能用在async定义的函数内,async函数会隐式地返回一个promise,该promise的resolve值就是函数return的值。
内存泄露
- JavaScript内存泄露指对象在不需要使用它时仍然存在,导致占用的内存不能使用或回收
- 未使用var声明的全局变量
- 闭包函数
- 循环引用(两个对象相互引用)
- 控制台日志
- 移除存在绑定事件的DOM元素
xss和csrf攻击
ajax和fetch
最后
以上就是危机方盒为你收集整理的前端面试题(js)1、js的数据类型2、原型,原型链3、闭包4、this指向5、DOM事件6、javascript的运行机制7、浏览器的渲染过程,DOM树和渲染书的区别?8、如何实现继承9、 cookie和storage的全部内容,希望文章能够帮你解决前端面试题(js)1、js的数据类型2、原型,原型链3、闭包4、this指向5、DOM事件6、javascript的运行机制7、浏览器的渲染过程,DOM树和渲染书的区别?8、如何实现继承9、 cookie和storage所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复