你不走出舒适圈,又怎么知道自己多坚强?!
前端面试 - JS总结(1) - 基础 (数据类型, 事件与函数, 原型链)
前端面试 - JS总结(2) - ES6 (let, 箭头函数, this)
目录
7. obj.toString() 和Object.prototype.toString.call(obj)的区别
15. null和undefine和not defined区别
18. Array.from 和 Array() 和 Array.of的区别
一、数据类型
1. 判断数据类型
JS基本有5种简单数据类型:String,Number,Boolean,Null,Undefined。引用数据类型:Object,Array,Function。
JavaScript一共有8种数据类型,其中有7种基本数据类型:Undefined、Null、Boolean、Number、String、Symbol(es6新增,表示独一无二的值)和BigInt(es10新增);1种引用数据类型:Object(Object本质上是由一组无序的名值对组成的)。里面包含 function、Array、Date等。
方法:typeof、instanceof、constructor、Object.prototype.toString.call()
(1) Typeof可以判断所有值类型,判断函数,判断是否为引用类型,但是不可细分,因此对于数组null无法进一步判断,因此需要用到instanceof。 (不能得到null:在 JS 的最初版本中,设置000 开头代表是对象,而null是全0,所以会误判为Object)
(2)instanceof只有引用数据类型(Array,Function,Object)被精准判断,其他(数值Number,布尔值Boolean,字符串String)字面值不能被instanceof精准判断。
(3) Constructor可以判断string,number, 几乎所有,然而,如果创建一个对象,更改它的原型,这种方式也变得不可靠了。
(4)Object.prototype.toString.call() 优点:精准判断数据类型 缺点:写法繁琐不容易记,推荐进行封装后使用
参考链接 https://juejin.im/post/5df1e312f265da33d039d06d
2. 判断是不是Array
- Array.isArray(obj)
- obj instanceof Array
- Object.prototype.toString.call(obj) == '[object Array]'
- obj.constructor === Array
3. 原始值VS引用值
- 原始数据类型:直接存储在栈(stack)中,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
- 引用数据类型:同时存储在栈(stack)和堆(heap)中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
原始变量及他们的值储存在栈中,当把一个原始变量传递给另一个原始变量时,是把一个栈房间的东西复制到另一个栈房间,且这两个原始变量互不影响。
引用值是把引用变量的名称储存在栈中,但是把其实际对象储存在堆中,且存在一个指针由变量名指向储存在堆中的实际对象,当把引用对象传递给另一个变量时,复制的其实是指向实际对象的指针,此时两者指向的是同一个数据,若通过方法改变其中一个变量的值,则访问另一个变量时,其值也会随之加以改变;但若不是通过方法而是通过重新赋值 ,此时相当于重新开了一个房间,该值的原指针改变,则另外一个值不会随他的改变而改变。
二、类型转换
4. 强制类型转换和隐式类型转换
强制:parseInt 、parseFloat、 toString等
隐式:if 、逻辑运算、== 、+拼接字符串,
alert会自动将任何类型的值转化为字符串显示,数学运算符则将值转换为数字。
5. 基本类型转换(3种)
ToNumber、ToBoolean 和 ToString 是针对一种基本类型值转换为另一种基本类型值,定义的转换规则;ToPrimitive 是针对对象类型转换为一种基本类型值,定义的转换规则,而且在必要时从对象类型值转换出来的基本类型值还要在进行一次转换到另一个基本类型值的换算。
(1)Tostring - 当一次运算的期望值是字符串的时候,就会发生 ToString
转换。
(2)ToNumber - 数字转换会自动发生于数学函数和表达式中。(+,-
、*
和 %
运算都会将操作数自动转换为数字。)(其中:undefined, Symbol转化为NaN)
但 +
运算符有一些例外:如果 +
运算符的一边是字符串,那么另一边的值也会自动转换为字符串再相加。
(3)Tobealen - 转为false:false 0 -0 null '' undefined NaN。 其它都转为true,包括空数组和空对象。(在 JavaScript 中,所有非空字符串都是 true
。因此’0‘ = true)
参考链接:https://learnku.com/articles/7380/javascript-type-conversion
6. 对象转化为基本类型
当一个对象期望值是基本类型值,就会发生对象转换为基本类型值。这里会用到 ToPrimitive
算法
但这要依赖环境,用所谓的 hint
表示,它可能的取值有 3 个。
(1) String -- 输出对象或者将对象作为属性使用
(2) number -- 数学运算
(3) default -- 适应于运算结果 “不确定” 的操作中。比如:二元加号既可以用来连接两个字符串,也可以用来把两个数字相加,二元加号也可以用在对象上。还有当一个对象使用 == 与字符串、数字、布尔值或者 Symbol 值比较的时候。
对象转换过程中,会尝试查找和调用的方法有 3 个:
- 如果 obj[Symbol.toPrimitive](hint) 方法存在,就调用。
- 否则,如果 hint 是 "string", 不论是否存在,尝试调用 obj.toString() 和 obj.valueOf()。
- 否则,如果 hint 是 number 或者 default, 不论是否存在,尝试调用 obj.valueOf() 和 obj.toString()。
在实践中,通常仅实现 obj.toString()
方法作为所有转换场景的唯一处理通道就足够了。
对象在转为值类型时,会调用valueOf
方法,需要转成string类型时调用toString
方法
valueOf
和toString
不是该对象自身的方法,而是它通过隐藏属性__proto__
调用原型链上的方法
相关例子:https://learnku.com/articles/7394/conversion-of-objects-to-basic-type-values-in-javascript
7. obj.toString() 和Object.prototype.toString.call(obj)的区别
- obj.toString()是将对象转为字符串
- Object.prototype.toString.call(obj)是检测对象的数据类型
- 因为toString为Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法
- 当对象调用toString方法时,调用的是重写的方法,实现的是对象转换为字符串类型
- 而检测数据类型,要使用Object原型的toString方法
三、运算符相关
8. == VS ===
对于 == 来说,如果对比双方的类型不一样的话,就会进行类型转换
对于 === 来说就简单多了,就是判断两者类型和值是否相同。(但NaN === NaN还是false)
9. == 运算符判断流程
- 先判断类型是否相同,若相同,则直接比大小
- 类型不同,进行类型转换
- 判断是否在比较
null
和undefined
,是的话返回true 。 即:console.log(null == undefined) // true - 判断是否在比较
string
和number
,是的话将string转换为number。即:console.log('3' == 3) // true - 判断其中一方是否为
boolean
,是的话将boolean转换为number - 判断其中一方是否为
object
,另一方是否为string、number或symbol
,是的话将object转换为值类型 - 两边都是object,只要
不是同一对象的不同引用
,都为false - 只要出现
NaN
,就是false,因为NaN自己都不等于NaN
10. +运算的类型转换规则
- JS中所有对象,在进行类型转换时,都先调用
valueOf
方法,返回它的原始值 - 如果返回的原始值不是number,再调用
toString
方法,返回它的字符串值 - 运算的一方是字符串,则另一方也会转换为字符串
- 一方不是字符串或数字,先转为数字,若原始值不为number,再转为字符串
- true转换为1,false、null转换为0,undefined转换为NaN
- 'a' + + 'b'得到的是'aNaN'
四、对象与继承
11. JSON.stringify和JSON.parse
JSON.stringify()的作用是将 JavaScript 对象转换为 JSON 字符串,而JSON.parse()可以将JSON字符串转为一个对象。
json.stringify的秒用:
(1)判断数组是否包含某对象,或者判断对象是否相等(借助indexof)
(2)让localStorage/sessionStorage可以存储对象。localStorage/sessionStorage默认只能存储字符串,而实际开发中,我们往往需要存储的数据多为对象类型,那么可以在存储时利用json.stringify()将对象转为字符串,而在取缓存时,只需配合json.parse()转回对象即可。
(3)实现对象深拷贝。 如果怕影响原数据,我们常深拷贝出一份数据做任意操作
JSON.stringify和toString: 都可以将目标值转为字符串。JSON.stringify()的受众更多是对象,而toString()虽然可以将数组转为字符串,但并不能对对象实现操作,它的受众更多是数字。
参考链接:https://www.cnblogs.com/echolun/p/9631836.html
12. JS内置对象
js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函 数对象。一般我们经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构 造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象。
13. 创建对象的方法
(1)工厂模式,工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。但是它有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码,而没有建立起对象和类型间的关系。
(2)构造函数模式,js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么我们就可以把它称为构造函数。执行构造函数首先会创建一个对象,然后将对象的原型指向构造函数的 prototype 属性,然后将执行上下文中的 this 指向这个对象,最后再执行整个函数,如果返回值不是对象,则返回新建的对象。因为 this 的值指向了新建的对象,因此我们可以使用 this 给对象赋值。构造函数模式相对于工厂模式的优点是,所创建的对象和构造函数建立起了联系,因此我们可以通过原型来识别对象的类型。但是构造函数存在一个缺点就是,造成了不必要的函数对象的创建,因为在 js 中函数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次我们都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。
(3)原型模式,因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。因此我们可以使用原型对象来添加公用属性和方法,从而实现代码的复用。这种方式相对于构造函数模式来说,解决了函数对象的复用问题。但是这种模式也存在一些问题,一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。
(4)组合使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存在一些问题,因此我们可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好
参考链接:https://mp.weixin.qq.com/s/S1fQ8TryUtCPgjegaKlBtg
14. 继承的实现方式
(1)原型链的方式,缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。
(2)借用构造函数,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。
(3)组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。
(4)原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。
(5)寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。
(6)寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。
15. null和undefine和not defined区别
- null 表示没有对象,转化为数值时为 0
- undefined 表示缺少值,转化为数值时为 NaN,定义但是未初始化
- not defined表示没有定义
(1) undefined 典型用法:
- 变量被声明了,但没有赋值时,就等于 undefined
- 调用函数时,应该提供的参数没有提供,该参数等于 undefined
- 对象没有赋值的属性,该属性的值为 undefined
- 函数没有返回值时,默认返回 undefined
(2) null 典型用法:
- 作为函数的参数,表示该函数的参数不是对象
- 作为对象原型链的终点
16. 遍历数组有哪些方式
- 普通 for 循环
- foreach 循环
- for in 循环
- map 遍历
- for of 遍历
17. JS变量互换的方法
- 建中间变量
- 异或 a = a^b; b=a^b; a=a^b
- a = a + b; b = a - b; a = a - b;
- console.log("a="+b);console.log("b="+a);
- a=[b,b=a][0];
- var [a,b] = [b,a]
18. Array.from 和 Array() 和 Array.of的区别
- Array.from可以将可遍历的对象转为真正的数组,比如字符串
- Array()当参数不少于2个时,返回由参数组成的新数组,当参数为1个时,返回长度为参数的新数组
- Array.of不管几个参数,都返回由参数组成的新数组
五、DOM和DOM
19. BOM是什么
javascript是由ECMAScript,DOM,BOM三部分构成的。
BOM
是浏览器对象模型,用于访问浏览器的功能. 包括浏览器的一些操作,window.onload
, window.open
等还有浏览器时间,监听窗口的改变onresize
,监听滚动事件onscroll
等;
(1)window对象:表示浏览器窗口
- scrollTo():滚动到指定的坐标
- setTimeout():定时器,在指定的时间后调用函数
- setInterval():定时器,按照指定的周期调用函数
(2)history对象:包含浏览器窗口访问过的URL
- back():加载前一个URL
- forward():加载下一个URL
- go():加载某个指定的URL
(3)navigator对象:包含浏览器的信息
(4)location对象:包含当前URL的信息
(5)screen对象:包含显示屏的信息
20. DOM是什么
DOM
是文档对象模型,包括了获取元素,修改样式以及操作元素等三方面的内容,是 HTML 和 XML 文档的编程接口。DOM是树结构
(1) 创建新节点:
- createDocumentFragment() // 创建一个DOM片段
- createElement() // 创建一个具体的元素
- createTextNode() // 创建一个文本节点
(2) 添加、移除、替换、插入:
- appendChild() // 添加
- removeChild() // 移除
- replaceChild() // 替换
- insertBefore() // 在已有的子节点前插入一个新的子节点
(3) 查找:
- getElementsByTagName() // 通过标签名称
- getElementsByName() // 通过元素的Name属性的值
- getElementById() // 通过元素Id,唯一性
21. DOM事件流
- 当一个HTML元素产生一个事件时,该事件会在元素节点与根节点之间的路径传播,路径所经过的节点都会收到该事件,这个传播的过程叫 做DOM事件流
- DOM标准采用捕获+冒泡。两种事件流都会触发DOM的所有对象,从window对象开始,也在window对象结束
- DOM标准规定事件流包括三个阶段:
- 事件捕获阶段:事件由父元素到子元素传递的过程
- 处于目标阶段:事件通过捕获到达目标元素
- 事件冒泡阶段:事件由子元素传递到父元素的过程
六、事件和函数
22. 三种事件模型是什么?
事件 是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型。
- DOM0级模型: ,这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过 js属性来指定监听函数。这种方式是所有浏览器都兼容的。
- IE 事件模型: 在该事件模型中,一次事件共有两个过程,事件处理阶段,和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行。
- DOM2 级事件模型: 在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。
23. 什么是事件传播?
当事件发生在DOM元素上时,该事件并不完全发生在那个元素上。在“当事件发生在DOM元素上时,该事件并不完全发生在那个元素上。
事件传播有三个阶段:
-
捕获阶段–事件从 window 开始,然后向下到每个元素,直到到达目标元素事件或event.target。
-
目标阶段–事件已达到目标元素。
-
冒泡阶段–事件从目标元素冒泡,然后上升到每个元素,直到到达 window。
24. 事件冒泡 和 事件代理/委托
事件冒泡是指嵌套最深的元素触发一个事件,然后这个事件顺着嵌套顺序在父元素上触发。
事件委托 本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到 目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件. 使用事件代理我们可以不必要为每一个子元素都绑定一个监听事件,这样减少了内存上的消耗。 优点:大量节省内存占用,减少事件注册; 当新增子元素时,无需再次对其绑定
冒泡阻止方式:使用event.cancelBubble = true
或者event.stopPropgation()
(低于IE9)。
默认事件阻止方式: e.preventDefault();
或return false;
。
25. 常见取消事件函数
(1)preventDefault() 取消事件默认行为,如阻止点击提交按钮对表单的操作
(2)stopImmediatePropagation() 取消事件冒泡同时阻止当前节点上的事件处理程序被调用,影响当前事件
(3)stopPropagation()取消事件冒泡,不影响事件
(4)cancelBubble()取消事件冒泡
(5)returnValue()取消事件默认行为
26. IE 的事件处理和 W3C 的事件处理
- 绑定事件:attachEvent 和 addEventListener
- 删除事件:detachEvent 和 removeEventListener
- 事件目标:window.event.srcElement 和 event.target
- 阻止事件默认行为:window.event.returnValue = false 和 event.preventDefault()
- 阻止事件传播:window.event.cancelBubble = true 和 event.stopPropagation()
27. Event对象的属性和方法
- event.target:触发事件的元素(目标元素)
- event.currentTarget:绑定事件的元素(父级元素)
- event.type:当前事件的名称
- event.preventDefault():阻止默认行为
- event.stopPropagation():阻止在捕获阶段或冒泡阶段继续传播,而不是阻止冒泡
- event.stopImmediatePropagation():阻止事件冒泡
28. JS定时器
定时器用于在设定的时间执行一段代码,或者在给定的时间间隔内重复该代码。通过函数setTimeout
、setInterval
和clearInterval
来完成。
- setTimeout(
function
,delay
)函数用于移动在所述延迟之后调用特定功能的定时器; - setInterval(
function
,delay
)函数用于在提到的延迟中重复执行给定的功能,只有在取消时才能停止; - clearInterval(
id
)函数指示定时器停止;
29. 什么是 IIFE
它是立即调用函数表达式(Immediately-Invoked Function Expression),简称 IIFE。函数被创建后立即被执行:
30. 匿名函数
- 定义回调函数;
- 立即执行函数;
- 作为返回值的函数;
- 使用方法为
var func = function() {};
定义的函数;
七、原型和原型链
31. 原型的概念
- 中所有对象都包含一个__proto__内部属性,这个属性对应的就是这个对象的原型
- JS的函数对象,除了隐藏属性__proto__外,还有prototype属性
- 当函数对象作为构造函数创建对象时,它的prototype就是实例对象的__proto__
32. 原型链的概念
- 原型链就是原型组成的链
- 实例对象的__proto__属性就是它的原型,该原型就是实例对象原型链上的第一个原型
- 而原型也是一个对象,也有__proto__属性,原型的__proto__属性又是原型的原型
- 就这样一直通过__proto__往上找,当找到基类Object的原型时,原型链就结束了,即基类Object的原型是该实例对象的原型链上的最后一个原型
- 实例对象的原型链上的所有原型对象的属性和方法都是实例所共享的
- 一个对象在调用一个方法或属性时,他会先在自己身上找,找不到时,他会沿着原型链依次向上查找
- 函数才有prototype,实例对象只有有__proto__, 而函数有__proto__是因为函数是Function的实例对象
33. js 获取原型的方法?
-
p.proto
-
p.constructor.prototype
-
Object.getPrototypeOf(p)
34. 继承方法
- 原型链继承;
- 构造函数继承;
- 实例继承;
- 组合继承;
- 拷贝继承;
- 寄生组合继承;
35. 使用构造函数创建对象时,new的原理
- 创建一个空对象obj,作为要返回的实例对象
- 将obj的__proto__指向了构造函数(比如Base)的prototype
- 构造函数对象的this指针替换成obj, 相当于执行了Base.call(obj);
- 如果构造函数显式的返回一个对象,那么这个实例对象为这个返回的对象,否则为这个新创建的对象obj