Hello,今天给各位童鞋们分享的是JS面试习题,赶紧拿出小本子记下来吧
1.关于闭包什么是闭包?
闭包是有权限访问其它函数作用域内的变量的一个函数。
在js中,变量分为全局变量和局部变量,局部变量的作用域属于函数作用域,在函数执行完以后作用域就会被销毁,内存也会被回收,但是由于闭包是建立在函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会被销毁,此时的子函数——也就是闭包,便拥有了访问上级作用域中变量的权限,即使上级函数执行完以后作用域内的值也不会被销毁。
闭包有三个特性:
1.函数嵌套函数
2.函数内部可以引用外部的参数和变量
3.参数和变量不会被垃圾回收机制回收
闭包解决了什么?
本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。由于闭包可以缓存上级作用域,这样函数外部就可以访问到函数内部的变量。
闭包的应用场景
ajax请求成功的回调 一个事件绑定的回调方法 setTimeout的延时回调 一个函数内部返回另一个匿名函数
闭包的优缺点
优点: 让代码更加规范、简洁
缺点: 使用闭包过多,内存消耗大,造成内存的泄露
2.原型和原型链(1) 所有的引用类型都有一个_proto_(隐式原型)属性,属性值是一个普通的对象
(2) 所有的函数除了有_proto_属性,还都有一个prototype(显式原型)属性,属性值是一个普通的对象
(3) 所有引用类型的_proto_属性指向它构造函数的prototype
当一个对象调用自身不存在的属性/方法时,会先去它的_proto_上查找,也就是它的构造函数的prototype;如果没有找到,就会去该构造函数的prototype的_proto_指向的上一级函数的prototype中查找,再往上会指向对象的prototype,最后指向null。这样一层一层向上查找的关系会形成一个链式结构,称为原型链。
3.ES5继承和ES6继承ES5: 组合式继承,先创建子类的实例对象,然后再将父类的方法通过call方法添加到this上,通过原型和构造函数的机制来实现
示例:
ES6: 先创建父类的实例对象this(所以必须先调用父类的super()方法,然后再用子类的构造函数修改this),通过class关键字定义类,类之间通过extends关键字实现继承,子类必须在constructor方法中调用super方法。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其加工,如果不调用super方法,子类得不到this对象。
示例:
4.原生AJAX请求步骤五步使用法:
(1) 创建XML请求:
POST请求:
5.关于事件委托什么是事件委托
事件委托也叫事件代理,就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
事件委托的作用
(1) 提高性能:每一个函数都会占用内存空间,只需添加一个时间处理程序代理所有事件,所占用的内存空间更少;
(2) 动态监听:使用事件委托可以自动绑定动态添加的元素,即新增的节点不需要主动添加也可以具有和其它元素一样的事件。
实现方式 我们先来看看,如果不用事件委托,需要绑定多个相同事件的时候是如何实现的:
不使用事件委托,那就要遍历每一个li元素,给每个li元素绑定一个点击事件,这样的做法非常耗费内存,如果有100个、1000个li元素,那对性能的影响是非常大的。
那么使用事件委托是怎么实现的呢?
这样一来,通过事件委托,只需要在li元素的父元素ul上绑定一个点击事件,通过事件冒泡的机制,就可以实现li的点击效果。并且通过js动态添加li元素,也能绑定点击事件。
6.null不是一个对象,但为什么typeof null === object原理是这样的,不同的对象在底层都会表示为二进制,在js中如果二进制的前三位都为0,就会被判断为object类型,null的二进制全为0,自然前三位也是0,所以typeof null === objcet。
7.关于浅拷贝和深拷贝浅拷贝: 只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
实现:
方法一:直接用=赋值
let obj1 = {a: 1}
let obj2 = obj1
方法二:Object.assign
let obj1 = {a: 1}
let obj2 = {}
Object.assign(obj2, obj1)
方法三:for in循环只遍历第一层
深拷贝:
方法一:用 JSON.stringify 把对象转换成字符串,再用 JSON.parse 把字符串转换成新的对象
方法二:采用递归去拷贝所有层级属性
方法三:lodash函数库实现深拷贝
方法四:通过jQuery的extend方法实现深拷贝
let array = [1,2,3,4]
let newArray = $.extend(true,[],array) // true为深拷贝,false为浅拷贝
方法五:用slice实现对数组的深拷贝
方法六:使用扩展运算符实现深拷贝
let obj1 = {brand: "BMW", price: "380000", length: "5米"}
let obj2 = { ...car, price: "500000"}
8.谈谈js的垃圾回收机制js拥有自动的垃圾回收机制,当一个值在内存中失去引用时,垃圾回收机制会根据特殊的算法找到它,并将其回收,释放内存。
标记清除法(常用)
(1) 标记阶段:垃圾回收器会从根对象开始遍历。每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象
(2) 清除阶段:垃圾回收器会对堆内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作;
优点:实现简单
缺点:可能会造成大量的内存碎片
引用计数清除法
(1) 引用计数的含义就是跟踪记录每个值被引用的次数,当声明了一个变量并将一个引用类型赋值给该变量时,这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,这个值的引用次数就减1。
(2) 当这个引用次数变成0时,则说明没有办法再访问这个值了,就可以将其所占的内存空间给回收。这样,垃圾收集器下次再运行时,就会释放那些引用次数为0的值所占的内存。
优点: (1) 可即刻回收垃圾
缺点: (1) 计数器值的增减处理繁重
(2) 实现繁琐复杂
(3) 循环引用无法回收
9.如何阻止事件冒泡和默认事件标准的DOM对象中可以使用事件对象的stopPropagation()方法来阻止事件冒泡,但在IE8以下中的事件对象通过设置事件对象的cancelBubble属性为true来阻止冒泡默认事件通过事件对象的preventDefault()方法来阻止,而IE通过设置事件对象的returnValue属性为false来阻止默认事件10.函数防抖和函数节流函数防抖(debounce): 当调用函数n秒后,才会执行该动作,若在这n秒内又调用该函数则取消前一次并重新计算执行时间(频繁触发的情况下,只有足够的空闲时间,才执行代码一次)
函数节流(throttle): 函数节流的基本思想是函数预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期(一定时间内js方法只跑一次。比如人的眨眼睛,就是一定时间内眨一次)
11.谈谈js的事件循环机制程序开始执行之后,主程序则开始执行同步任务,碰到异步任务就把它放到任务队列之中,等到同步任务全部执行完后,js引擎便去查看任务队列有没有可以执行的异步任务,将异步任务转成同步任务并开始执行,执行完同步任务后继续查看任务队列。这个过程是一直循环的,因此这个过程就是所谓的事件循环,其中任务队列也被称为事件队列。通过一个任务队列,单线程的js实现了异步任务的执行,给人的感觉好像是多线程的。
12.箭头函数和普通函数的区别箭头函数:
(1)在使用 => 定义函数的时候,this的指向是定义时所在的对象,而不是使用时所在的对象,bind()、call()、apply()均无法改变指向
(2) 不能用做构造函数,也就是说不能使用new命令,否则就会抛出一个错误
(3) 不能使用arguments对象,但是可以使用…rest参数
(4) 不能使用yield命令
(5) 没有原型属性
普通函数:
(1) this总是代表它的直接调用者
(2) 在默认情况下,没找到直接调用者,this指向window
(3) 在严格模式下,没有直接调用者的函数中的this是undefined
(4) 使用call,apply,bind绑定,this指的是绑定的对象
13.call()、apply()、bind()的区别call()、apply()、bind()是用来改变this的指向的
call(): Function.call(obj, param1,param2,param3) 接收到的是param1,param2,param3三个参数
apply(): Function.apply(obj, [param1,param2,param3]) 接收到的是param1,param2,param3三个参数
call和apply的区别是参数一个不用[],一个要用[]
bind(): const newFn = Funtion.bind(obj, param1,param2) 返回值是一个函数,需要()来调用 newFn(param3,param4) 接收到的是param1,param2,param3,param4四个参数
14.进程和线程的区别进程(process): 是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位),是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念。
线程(thread): 是cpu调度的最小单位(是建立在进程基础上的一次程序运行单位),是进程内可调度的实体,比进程更小的独立运行的基本单位。
一个进程有一个或多个线程,线程之间共同完成进程分配下来的任务,打个比方:● 假如进程是一个工厂,工厂有它的独立的资源 ● 工厂之间相互独立 ● 线程是工厂中的工人,多个工人协作完成任务 ● 工厂内有一个或多个工人 ● 工人之间共享空间
再完善完善概念:
● 工厂的资源 -> 系统分配的内存(独立的一块内存)
● 工厂之间的相互独立 -> 进程之间相互独立
● 多个工人协作完成任务 -> 多个线程在进程中协作完成任务
● 工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成
● 工人之间共享空间 -> 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)
15.ES6的新特性ES6的特性
(1) 类(class) 对熟悉Java、C、C++等语言的开发者来说,class一点都不陌生。ES6引入了class(类),让JS的面向对象编程变得更加简单和易于理解。
(2) 模块化(Module) ES5不支持原生的模块化,在ES6中模块作为重要的组成部分被添加进来。模块的功能主要由export和import组成。每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口,通过import来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。
导出(export) ES6运行在一个模块中使用export来导出多个变量或函数
导入(import) 定义好模块的输出以后就可以在另外一个模块通过import引用。
import{myModule} from 'myModule'
import{a,b} from 'test'
(3) 箭头(Arrow) 函数 这是ES6中最令人激动的特性之一。=>不只是关键字function的简写,它还带来了其它好处。箭头函数与包围它的代码共享同一个this,能很好的解决this的指向问题。
(4) 函数参数默认值 ES6 支持在定义函数的时候为其设置默认值,当函数的参数为布尔值false时,可以规避一些问题
(5) 模板字符串
(6) 解构赋值 通过解构赋值可以方便的交换两个变量的值:
获取对象中的值:
(7) 延展操作符(Spread operator)和剩余运算符(rest operator) 当三个点(…)在等号右边,或者放在实参上,是 spread运算符
当三个点(…)在等号左边,或者放在形参上,是 rest 运算符
(8) 对象属性简写 在ES6中允许我们在设置一个对象的属性的时候不指定属性名不使用ES6:
使用ES6:
(9) Promise Promise 是异步编程的一种解决方案,比传统的解决方案callback更加的优雅。它最早由社区提出和实现的,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
不使用ES6
使用ES6
(10) 支持let与const 在之前JS是没有块级作用域的,const与let填补了这方面的空白,const与let都是块级作用域。
let和var的区别:
● let没有变量提升,存在暂时性死区,必须等let声明完以后,变量才能使用
● let变量不能重复声明
● let声明的变量只在let代码块有效
16.new关键字做了什么?
使用new操作符调用构造函数实际上会经历以下4个步骤:
(1) 创建一个新对象
(2) 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
(3) 执行构造函数中的代码(为这个新对象添加属性、方法)
(4) 返回新对象
好啦,今天的文章就到这里了,希望能够帮助到屏幕前迷茫的你们