1. JavaScript有哪些数据类型?
JavaScript共有八种数据类型,分别是 Number、Boolean、String、Undefined、Null、Object、Symbol、BigInt。
2. 如何判断一个数据是NaN
NaN是非数字,但是用 typeof
检测是number类型。
- 用
typeof
判断是否是number类型并且判断是否满足isNaN - 利用
NaN
是唯一一个不等于任何自身的特点 n !== n - 利用ES6中提供的
Object.is()
方法(判断两个值是否相等)
3. JS中 null
和 undefined
的区别
相同点:隐式类型转换都是false 不同点:number(null) 是0,number(undefined)是NaN
null表示值被定义了,但是是空值 undefined表示未被定义
4. 闭包是什么?
闭包可以简单被理解成:定义在一个函数内部的函数。其中一个内部函数在包含它们的外部函数之外被调用时,就会形成闭包。
特点:
- 函数嵌套函数
- 函数内部可以引用外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
优点:
- 变量长期驻扎内存
- 避免全局变量污染
- 私有成员的存在
缺点:
- 使用不当会造成内存泄漏
5. JS中常见的内存泄露
- 意外的全局变量
- 被遗忘的计时器或回调函数
- 脱离DOM的引用
- 闭包
6. 事件委托是什么?
事件委托就是利用事件冒泡,只定制一个事件处理程序,就可以管理某一类型的所有事件。
事件委托,称为事件代理,是JS中很常用的绑定事件的方法。事件委托就是把原本需要绑定在子元素上面的事件委托给父元素,让父元素担当事件监听的职务,原理是DOM元素的事件冒泡。
7. 什么是事件冒泡
一个事件触发后,会在子元素和父元素之间传播,这种传播分为三个阶段:
- 捕获阶段(从 window 对象传导到目标节点(从外到里),这个阶段不会响应任何事件)
- 目标阶段(在目标节点上触发)
- 冒泡阶段(从目标节点传导回window对象,从里到外。) 事件委托/事件代理就是利用事件冒泡的机制把里层需要响应的事件绑定到外层
8. 本地存储与cookie的区别
相同点:
都是在浏览器(客户端)内存储,存储方式为字符串
不同点:
1、生命周期:
1)cookie如果不设置有效期,就是临时存储(存储在内存中),如果设置了有效期,到期之后会消失(存储在硬盘里)
2)localStorage是永久存储,关闭浏览器数据也不会消失,除非手动删除
3)sessionStorage是临时存储,仅在当前回话下有效。引入了一个窗口的概念,数据仅在当前窗口下生效,关闭当前窗口的话数据会被删除
2、cookie数据在每次网络请求的时候都会被发送给服务端,localStorage和sessionStorage则不会
3、cookie大小限制为4kb,storage则为5M
4、storage比cookie的安全性更高一些
9. let、const和var的区别
- var声明的变量会挂载在window上,而let和const则不会
- var声明的变量存在变量提升,而let和const则不会
- 同一个作用域下var可以声明同名变量,而let和const则不行
- let和const会生成块级作用域
- const一旦声明之后不可修改,如果是复杂数据类型的话,可以修改其属性
10. 数组方法
- push(),从后面添加元素,返回值为添加完元素后的数组长度
- pop(),从后面删除元素,只能删除一个,返回值为删除的元素
- shift(),从前面删除元素,只能删除一个,返回值为删除的元素
- unshift(),从前面添加元素,返回值是添加完元素后的数组长度
- splice(i, l),删除从i开始的,长度为l的元素,返回值是删除的元素
- concat(),连接两个数组,返回值为连接后的新数组
- split(),将字符串转换为数组
- sort(),将数组进行排序,返回值是排好的数组,默认是按照最左边的数字进行排序,不是按照数字大小排序的
- reverse(),反转数组,返回值是反转后的数组
- slice(start, end),切去索引值start到end的数组,不包含end,返回值是切出来的数组
- forEach(),遍历数组,没有返回值
- map(),遍历数组,返回值为一个新数组
- filter(),过滤数组,返回一个满足要求的数组
11. 普通函数与构造函数的区别
- 构造函数也是一个普通函数,创建方式和普通函数一致,但是构造函数习惯性首字母大写
- 调用方式不一样,普通函数直接调用,构造函数要用new去实例化
- 调用时,构造函数内部会创建一个新对象,普通函数不会创建新对象
- 构造函数内部的this指向实例,普通函数内部的this指向调用函数的对象,如果没有调用对象,则默认为window
- 构造函数默认的返回值是创建的对象,普通函数的返回值由内部的return决定
- 构造函数的函数名与类名相同
12. 原型/原型链/继承
什么是原型:
任何对象实例都有一个原型,也叫原型对象。这个原型对象由对象的内置属性_proto_指向它的构造函数的prototype指向的对象,即任何对象都是由一个构造函数创建的,但是不是每一个对象都有prototype,只有方法才有prototype。
什么是原型链
原型链基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法,我们知道,每个构造函数都有一个原型对象,每个原型对象都有一个指向构造函数的指针,而实例又包涵一个指向原型对象的内部指针。
原型链的核心就是依赖对象的_proto_的指向,当自身不存在的属性时,就一层层的扒出创建对象的构造函数,直至到Object时,就没有_proto_指向了。
因为_proto_实质找的是prototype,所以我们只要找这个链条上的构造函数的prototype,其中Object.prototype是没有_proto_属性的,它==null
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含指向原型对象内部的指针。
原型继承是 js 的一种继承方式,原型链作为实现继承的主要方法,其基本思路是利用原型让一个引用类型继承另一个引用类型的属性和方法,
原型继承:利用原型中的成员可以被和其相关的对象共享这一特性,可以实现继承,这种实现继承的方式,就叫做原型继承.
13. Promise的理解
什么是Promise?
我们都知道,Promise是承诺的意思,承诺它过一段时间会给你一个结果
Promise是一种解决异步编程的方案,相比回调函数和事件更合理、更强大
从语法上讲,Promise是一个对象,从它可以获取异步操作的消息
Promise的状态
- pending 等待状态
- rejected 失败状态
- fulfiled 成功状态
状态一旦改变,就不会再变
Promise的特点
- Promise对象不受外界影响
- Promise的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可逆
Promise的缺点
- 无法取消Promise,一旦新建就会立即执行,无法中途取消
- 如果不设置回调函数,Promise内部抛出错误,不会反映到外部
- 当处于pending状态时,无法得知目前进展到哪一阶段
14. 请简述async的用法
async就是Generation和Promise的语法糖,async就是将Generator的*转换成async,将yield转换成await
函数前必须加一个async,异步操作方法前加一个await关键字,意思就是等一下,执行完了再继续走。注意:await 只能在 async 函数中运行,否则会报错
Promise 如果返回的是一个错误的结果,如果没有做异常处理,就会报错,所以用 try…catch 捕获一下异常就可以了
15. 一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?
当发送一个URL请求时,不管这个URL是web页面URl还是Web页面上每个资源的URL,浏览器都会开启一个线程来处理这个请求,同时在远程DNS服务器上启动一个DNS查询。这能使得浏览器得到请求对应的IP地址。
浏览器与远程web服务器通过TCP三次握手协商来建立一个TCP/IP连接,该握手包括一个同步报文,一个同步-应答报文和一个应答报文。这三个报文在浏览器和服务器之间传递。该握手首先由客户端尝试建立起通信,然后服务器相应并接受客户端的请求,最后由客户端发出该请求已被接受的报文。
一旦TCP/IP连接建立,浏览器会通过该连接向远程服务器发送HTTP的GET请求。远程服务器找到资源并使用HTTP相应返回该资源
16. GET请求传参长度的误区
我们常说GET请求参数的大小存在限制,而POST请求的参数大小是无限制的。
实际上HTTP协议从未规定GET/POST的请求长度是多少,对GET请求参数的限制是来源与浏览器或WEB服务器,浏览器或WEB服务器限制了url的长度。为了明确这个概念,我们必须强调下面几点。
- HTTP协议未规定 GET 和 POST 的长度限制
- GET 的最大长度显示是因为浏览器和web服务器限制了 URI 的
- 长度不同的浏览器和 WEB 服务器,限制的最大长度不一样要支持
- IE,则最大长度为 2083byte,若只支持 Chrome,则最大长度8182byte
17. 说说前端中的事件流
HTML
与 JS
交互是通过事件驱动来实现的,例如鼠标点击事件 onclick
、页面滚动事件 onscroll
等等。可以向文档或者文档中的元素添加事件监听来订阅事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下 ”事件流“ 的概念。
什么是事件流
事件流描述的是从页面中接收事件的顺序,DOM2
级事件流包括下面几个阶段:
事件捕获阶段处于目标阶段事件冒泡阶段
addEventListener:addEventListener
是 DOM2
级事件新增的指定事件处理程序的操作,这个方法接收 3
个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true
,表示在捕获阶段调用事件处理程序;如果是 false
,表示在冒泡阶段调用事件处理程序。IE只支持事件冒泡。
18. 说一下事件委托
事件委托是指,不在事件的发生地(直接DOM)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素DOM的类型,来做出不同的相应。
举例:最经典的就是ul和li标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在li标签上直接添加,而是在父元素ul上面添加
好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。
18. JS的new操作符做了哪些事情
new操作符新建了一个空对象,这个对象原型指向构造函数的prototype,执行构造函数后返回这个对象。
19. 改变函数内部 this
指针的函数(bind、apply、call的区别)
通过 apply
和 call
改变this指向,他们两个函数的第一个参数都是一样的表示要改变指向的那个对象,第二个参数:apply
是数组,而call
则是arg1,arg2…这种形式。通过bind改变this作用域会返回一个新的函数,这个函数不会马上执行。
20. JS中各种位置clientHeight、scrollHeight、offsetHeight、以及scrollTop、offsetTop、clientTop的区别?
- clientHeight:表示的是可视区域的高度,不包含border和滚动条。
- offsetHeight:表示可视区域的高度,包含了border和滚动条。
- scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分。
- clientTop:表示边框border的厚度,在未指定的情况下一般为0。
- scrollTop:滚动后被隐藏的高度,获取对象相对于由offsetParent属性指引的父坐标(CSS定位的元素或body元素)距离顶端的高度
21. JS中的垃圾回收机制
必要性: 由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JS程序每次创建字符串、数组或者对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则JS的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。JS的解释器可以检测到何时程序不再使用一个对象了,当它确定了一个对象是无用的时候,它就知道不再需要这个对象,可以把它所占用的内存释放掉了。
例如:
var a = "hello word";
var b = "hello wrod";
var a = b;
复制代码
这时,会释放掉"hello word",释放内存以便再引用垃圾回收的方法:标记清除、计数引用。
标记清除:
这时最常见的垃圾回收方式,当变量进入环境时,就标记这个变量为”进入环境“,从逻辑上讲,永远不能释放进入环境的变量所占用的内存,只要执行流程进入相应的环境,就可能用到他们。当离开环境时,就标记为离开环境。垃圾回收器在运行的时候会给存储在内存中的变量都加上标记,然后去掉环境变量中的变量,以及被环境变量中的变量所引用的变量(条件性去除标记),删除所有被标记的变量,删除的变量无法在环境变量中被访问所以会被删除,最后垃圾回收器完成了内存的清除工作,并回收他们所占用的内存。
引用计数法:
另一种不太常见的方法就是引用计数法,引用计数法的意思就是每个值被引用的次数,当声明了一个变量,并用一个引用类型的值赋值给变量,则这个值的引用次数为 1
,相反的,如果包含了对这个值引用的变量又取得了另外一个值,则原先的引用值引用次数就减 1
,当这个值的引用次数为 0
时,说明没有办法再访问这个值了,因此就把所占的内存给回收进来,这样垃圾收集器再次运行的时候,就会释放引用次数为 0
的这些值。
用引用计数法会存在内存泄漏:
function problem {
var objA = new Object();
var objB = new Object();
objA.a = objB;
objB.b = objA;
}
复制代码
在这个栗子里面,objA和objB通过各自的属性互相引用,这样的话,两个对象的引用次数都为 2
,在采用引用计数的策略中,由于函数执行之后,这两个对象都离开了作用域,函数执行完成后,因为计数不为 0
,这样的相互引用如果大量存在就会导致内存泄漏。
特别是在DOM对象中,也容易存在这种问题,
var element = document.getElementById('');
var myObj = new Object();
myObj.element = element;
element.a = myObj;
复制代码
这样就不会有垃圾回收的过程。
22. 监听对象属性的改变
在ES5里面可以通过Object.defineProperty来实现已有属性的监听
Object.defineProperty(user, 'name', {
set: function(key, value) {
}
})
复制代码
缺点:如果id不在user对象中,则不能监听id的变化。
在ES6中可以通过Proxy来实现
var user = new Proxy({}, {
set: function(target, key, value, receiver) {
}
});
复制代码
这样即使有属性在Proxy中不存在,通过user.id来定义也同样可以这样监听这个属性的变化
23. 自己实现一个bind函数
原理:通过apply或者call方法来实现
(1) 初始版本
Function.prototype.bind = function(obj, arg) {
var arg = Array.prototype.slice.call(arguments, 1);
var context = this;
return function(newArg) {
arg = arg.concat(Array.prototype.slice.call(newArg));
return context.apply(obj, arg);
}
}
复制代码
(2) 考虑到原型链(因为在new一个bind生成的新函数的时候,必须的条件是要继承原函数的原型)
Function.prototype.bind = function(obj, arg) {
var arg = Array.prototype.slice.call(arguments, 1);
var content = this;
var bound = function(newArg) {
arg = arg.concat(Array.prototype.slice.call(newArg));
return context.apply(obj, arg);
}
var F = function() {}
F.prototype = context.prototype;
bound.prototype = new F();
return bound;
}