js面试篇
1、前端事件流
事件流描述的是从页面中接受事件的顺序,可以分为:事件捕获阶段 处于目标阶段 事件冒泡阶段其中需要主要的是addeventListener这个函数 最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
1、事件捕获阶段:实际目标div在捕获阶段不会接受事件,也就是在捕获阶段,事件从document到再到
就停止了2、处于目标阶段:事件在div发生并处理,但是事件处理会被看成是冒泡阶段的一部分。
-
3、冒泡阶段:事件又传播回文档
阻止冒泡事件event.stopPropagation()
function stopBubble(e) {
if (e && e.stopPropagation) { // 如果提供了事件对象event 这说明不是IE浏览器
e.stopPropagation()
} else {
window.event.cancelBubble = true //IE方式阻止冒泡
}
}
阻止默认行为event.preventDefault()
function stopDefault(e) {
if (e && e.preventDefault) {
e.preventDefault()
} else {
// IE浏览器阻止函数器默认动作的行为
window.event.returnValue = false
}
}
事件如何先捕获后冒泡:
在DOM标准事件模型中,是先捕获后冒泡。但是如果要实现先冒泡后捕获的效果,对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后再执行捕获事件。
哪些事件不支持冒泡事件:
鼠标事件:mouserleave mouseenter
焦点事件:blur focus
UI事件:scroll resize
2、事件委托(提高性能)
简介:事件委托指的是,不在事件的(直接dom)上设置监听函数,而是在其父元素上设置监听函数。通过事件冒泡,父元素可以监听到子元素上事件的触发通过判断事件发生元素DOM的类型,来做出不同的响应。
- 举例子: 最经典的就是ui和li标签的事件监听,比如我们在添加事件的时候,采用事件委托机制,不会在li标签上直接添加,而是在ul父元素上添加
- 好处:可以比较合适动态元素的绑定,新添加的子元素也会监听函数,也可以有事件触发机制
3、js的new操作符做了什么
new操作符创建了一个空对象,这个对象原型指向构造函数的prototype,执行构造函数后返回这个对象。如果不要父类的属性跟方法,在函数的prototype上去new这个父类。
4、this的指向
- 1、当函数作为对象的方法被调用时,this就会指向该对象。
- 2、作为普通函数,this指向window。
- 3、构造器调用,this指向返回的这个对象。
- 4、箭头函数 箭头函数的this绑定看的是this所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上
4.1、箭头函数this的原理:
this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
4.2、怎么改变this的指向呢?
1.使用es6的箭头函数;2.在函数内部使用that = this;3.使用apply,call,bind; 4.new实例化一个对象
4.3、bind,apply,call的区别
通过apply和call改变函数的this指向,他们两个函数的第一个参数都是一样的表示要改变指向的那个对象,第二个参数,apply是数组,而call则是arg1,arg2...这种形式。bind一个是返回一个函数,并不会立即执行第二个是带参数(第一个参数要指向的this,后面的的参数用来传递
5、深浅拷贝
基本类型 引用类型
基本类型:undefined,null,Boolean,String,Number,Symbol在内存中占据固定大小,保存在栈内存中 引用类型:Object,Array,Date,Function,RegExp等 引用类型的值是对象 保存在堆内存中,栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址。
基本类型的复制:其实就是创建了一个新的副本给将这个值赋值给新变量, 改变值旧对象不会改变 引用类型的复制:其实就是复制了指针,这个最终都将指向同一个对象,改变其值新对象也会改变
注意:基本类型的比较 == 会进行类型转换
浅拷贝 深拷贝
仅仅就是复制了引用,彼此操作不影响,slice() concat() object.assign 在堆中重新分配内存,不同的地址,相同的值,互不影响的 JSON.parse()将一个js对象序列化为一个json字符串JSON.stringify()将json字符串反序列化为一个js对象 es6的展开 {...}
重新在堆栈中创建内存,拷贝前后对象的基本类型互不影响。只拷贝一层,不能对对象进行子对象进行拷贝 对对象中的子对象进行递归拷贝,拷贝前后两个对象互不影响
6、setTimeout和setInterval的机制
因为js是单线程的。浏览器遇到etTimeout和setInterval会先执行完当前的代码块,在此之前会把定时器推入浏览器的待执行时间队列里面,等到浏览器执行完当前代码之后会看下事件队列里有没有任务,有的话才执行定时器里的代码
7、前端跨域问题
同源策略(协议+端口号+域名要相同)
- 1、jsonp跨域(只能解决get)
原理:动态创建一个script标签。利用script标签的src属性不受同源策略限制,因为所有的src属性和href属性都不受同源策略的限制,可以请求第三方服务器资源内容
步骤:1.去创建一个script标签
2.script的src属性设置接口地址
3.接口参数,必须要带一个自定义函数名,要不然后台无法返回数据
4.通过定义函数名去接受返回的数据
2、document.domain 基础域名相同 子域名不同
3、window.name 利用在一个浏览器窗口内,载入所有的域名都是共享一个window.name
4、服务器设置对CORS的支持
原理:服务器设置Access-Control-Allow-Origin HTTP响应头之后,浏览器将会允许跨域请求-
利用h5新特性window.postMessage()
iframe元素创建包含另外一个文档的内联框架(行内框架)(setTimeout进行异步加载)
解释:浏览器中的浏览器!用于设置文本或者图形的浮动图文框或容器
它和跨域
1、document.domain 实现主域名相同,子域名不同的网页通信
都设置为超域:document.domain = 'demo.com'
2、window.postMessageht(data, url),h5的API,启动跨域通信
8、图片预加载和懒加载
8.1、预加载:
提前加载图片,当用户需要查看是可以直接从本地缓存中渲染
为什么要使用预加载:在网页加载之前,对一些主要内容进行加载,以提供用户更好的体验,减少等待时间。否则,如果一个页面的内容过于庞大,会出现留白。
解决页面留白的方案:
1.预加载
2.使用svg站位图片,将一些结构快速搭建起来,等待请求的数据来了之后,替换当前的占位符
实现预加载的方法:
1.使用html标签
2.使用Image对象
3.使用XMLHTTPRequest对像,但会精细控制预加载过程
8.2、懒加载(lazyload)
客户端优化,减少请求数和延迟请求数,提升用户体验,减少无效资源的加载,防止并发加载的资源过多会阻塞js的加载,影响网站的正常使用
原理:首先将页面上的图片的src属性设置为空字符串,而图片的真是路经则设置带data-original属性中,当页面滚动的时候需要去监听scroll事件,在scroll事件的回调中,判断我们的懒加载的图片是否进入到可视区域,如果图片在可视区域将图片的src属性设置为data-original的值,这样就可以实现延迟加载。
9、函数节流和防抖
防抖 节流
短时间内多次触发同一个事件,只执行最后一次,或者在开始时执行,中间不执行。比如公交车上车,要等待最后一个乘客上车 节流是连续触发事件的过程中以一定时间间隔执行函数。节流会稀释你的执行频率,比如每间隔1秒钟,只会执行一次函数,无论这1秒钟内触发了多少次事件
都为解决高频事件而来, scroll mousewhell mousemover touchmove onresize,后面有相应的代码实现函数的节流和防抖。
10、js垃圾回收机制
1.JS具有自动垃圾收集的机制
2.JS的内存生命周期(变量的生命)
1.分配你所需要的空间 var a = 20
2.使用分配带的内存(读写) alert(a + 10)
3.不适用的时候,释放内存空间 a = null
3.JS的垃圾收集器每隔固定的时间就执行一次释放操作,通用的是通过标记清除的算法
4.在局部作用域中,垃圾回收器很容易做出判断并回收,全局比较难,因此应避免全局变量
标记清除算法:js最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将他标记为'进入环境',
当变量离开(函数执行完后),就其标记为'离开环境'。垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,
然后去掉环境中的变量以及被环境中该变量所引用的变量(闭包)。在这些完成之后仍存在标记的就是要删除的变量了
11、一些检验方法
千万不要使用typeof来判断对象和数组,因为这种类型都会返回object。
- typeOf()是判断基本类型的Boolean,Number,symbol, undefined, String。
对于引用类型:除function,都返回object null返回object。 - installOf() 用来判断A是否是B的实例,installof检查的是原型。
- toString() 是Object的原型方法,对于 Object 对象,直接调用 toString() 就能返回 [Object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。
- hasOwnProperty()方法返回一个布尔值,指示对象自身属性中是否具有指定的属性,该方法会忽略掉那些从原型链上继承到的属性。
- isProperty()方法测试一个对象是否存在另一个对象的原型链上。
- valueof:所有对象都有valueof,如果存在任意原始值,他就默认将对象转化为表示它的原始值。如果对象是复合值,而却大部分对象无法真正表示一个原始值,因此默认的valueof()方法简单的返回对象本身,而不是返回原始值
12、splice和slice、map和forEach、 filter()、reduce()的区别
1.slice(start,end):方法可以从已有数组中返回选定的元素,返回一个新数组,包含从start到end(不包含该元素)的数组方法
注意:该方法不会更新原数组,而是返回一个子数组
2.splice():该方法想或者从数组中添加或删除项目,返回被删除的项目。(该方法会改变原数组)
splice(index, howmany,item1,...itemx)
·index参数:必须,整数规定添加或删除的位置,使用负数,从数组尾部规定位置
·howmany参数:必须,要删除的数量,
·item1..itemx:可选,向数组添加新项目
3.map():会返回一个全新的数组。使用于改变数据值的时候。会分配内存存储空间数组并返回,forEach()不会返回数据
4.forEach(): 不会返回任何有价值的东西,并且不打算改变数据,单纯的只是想用数据做一些事情,他允许callback更改原始数组的元素
5.reduce(): 方法接收一个函数作为累加器,数组中的每一个值(从左到右)开始缩减,最终计算一个值,不会改变原数组的值
6.filter(): 方法创建一个新数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。它里面通过function去做处理
13、js、css阻塞
js阻塞 css阻塞
所有浏览器在下载JS的时候,会阻止一切其他活动,比如其他资源的下载,内容的呈现等等。直到JS下载、解析、执行完毕后才开始继续并行下载其他资源并呈现内容。为了提高用户体验,新一代浏览器都支持并行下载JS,但是JS下载仍然会阻塞其它资源的下载(例如.图片,css文件等)。 因为浏览器会维持html中css和js的顺序,样式表必须在嵌入的JS执行前先加载、解析完。而嵌入的JS会阻塞后面的资源加载,所以就会出现上面CSS阻塞下载的情况。
14、类的创建和继承
(es6)中class, extends
14.1、 继承:
原型链继承: function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; 无法实现多继承
构造继承:使用父类的构造函数来增强子类实例。function Cat(name){Animal.call(this);this.name = name || 'Tom';} 无法继承父类原型链上的属性跟方法 installof去检验
实例继承:为父类实例添加新特性,作为子类实例的返回
拷贝继承:拷贝父类元素上的属性跟方法
组合继承:构造继承 + 原型继承的组合体
寄生组合继承:通过寄生方式,在构造继承上加一个Super函数(没有实例和方法) 让他的原型链指向父类的原型链 砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性
14.2 给两个构造函数A和B,如何实现A继承B (Object.prototype)
function A(....){} A.prototype...
function B(....){} B.prototype...
A.prototype = Object.create(B.prototype) 再A的构造函数里new B(props)
使用new一个函数的话,函数里的构造函数的参数就为undefined,里面的一些函数可能执行错误,因为this改变了
Object.create = function (o) {
var F = function () {};
F.prototype = o;
return new F();
};
15、闭包和原型
15.1、闭包的理解
- 1、内部函数可以访问定义他们外部函数的参数和变量。(作用域链的向上查找,把外围的作用域中的变量值存储在内存中而不是在函数调用完毕后销毁)设计私有的方法和变量,避免全局变量的污染
- 2、函数嵌套函数
- 3、本质是将函数内部和外部连接起来。优点是可以读取函数内部的变量,让这些变量的值始终保存在内存中,不会在函数被调用之后自动清除
15.2、闭包的缺陷:
1.闭包的缺点就是常驻内存会增大内存使用量,并且使用不当容易造成内存泄漏
2.如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。
15.3、内存的理解
内存溢出和内存泄漏(给的不够用| 用了不归还)
- 1、内存溢出:在程序中申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出
- 2、内存泄漏:在程序申请内存后,无法释放已申请的内存空间,一次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟到会被占光
举列子:闭包中的this,对象函数。匿名函数返回函数return function
15.4、作用域
作用域:(由当前环境与上层环境一系列的变量对象组成!!!保证当先执行环境里,有权访问的变量和函数是有序的,作用域链变量只能被向上访问)
定义:由当前环境与上层环境的一系列变量对象组成(函数嵌套函数,内部一级级往上有序访问变量或对象)
作用是:保证当前执行环境里,有权访问的变量和函数时有序的,作用域链的变量只能被向上访问
变量访问到window对象及被终止,作用域链向下访问是不允许的
1.改变作用域有 with try..中的catch,
2.所有为定义的直接赋值的变量自动声明为全局作用域
作用域:一套规则,管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称
查找变量(标识符就是变量或者函数名)(只用全局作用域和局部作用域)(作用域在它创建的时候就存在了)
代码执行分为两个阶段:
1.代码编译阶段:有编译器完成,将代码翻译可执行的代码,这个阶段会被确定
2.代码执行阶段:有js引擎完成,主要执行可执行的大妈,这个阶段执行上下文被创建(对象被创建)
执行上下文:一个看不见得对象,存在若干个属性和变量,它被调用的时候创建的。函数被调用查看的this指向的object,object就是上下文(只有被调用的时候创建)
15.5、作用域链
· 当代码在一个环境中执行时,会创建变量对象的一个作用域链,
举例子:var name ="Tom"
function sayHi () {
alert('Hi,'+name)
}
sayHi() //Hi, Tom
函数sayHi()的执行环境为全局环境,所以它的变量对象为window。当函数执行到name时,先查找局部环境,找到则换回,否则顺着作用域查找,在全局环境中,
找到name返回,这一查找变量的有序过程的依据就是作用域。
· 作用域链是保证执行环境有权访问的所有变量和函数的有序访问
15.6、原型链
原型链:函数的原型链对象constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针proto,该指针是指向上一层的原型对象,而上一层的原型对象的结构依然类似。因此可以利用proto一直指向Object的原型对象上,而Object原型对象用Object.prototype.proto=null表示原型链顶端。如此形成了js的原型链继承。同时所有的js对象都有Object的基本防范