大家好,本次总结了关于JavaScript的上百道高频面试考点,感谢大家的留言点赞收藏 💗
如果文中有不对、疑惑或者错字的地方,欢迎在评论区留言指正🌻
设置 length = 0
会清空数组,所以会返回 undefined
当你触发一个元素的事件的时候,该事件从该元素的祖先元素传递下去,此过程为捕获
,而到达此元素之后,又会向其祖先元素传播上去,此过程为冒泡
我们可以总结出区别:
e.target
:触发事件的元素e.currentTarget
:绑定事件的元素构造函数
获得 原型对象:- 构造函数.prototype
- 复制代码
对象实例
获得 父级原型对象
:- 方法一: 对象实例.__proto__ 【 有兼容性问题,不建议使用】
- 方法二:Object.getPrototypeOf(对象实例)
- 复制代码
Math.ceil()
: 向上取整,函数返回一个大于或等于给定数字的最小整数。
Math.floor()
: 向下取整,函数返回一个小于或等于给定数字的最大整数。
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
它的核心就在于它认为自任何站点装载的信赖内容是不安全的。当被浏览器半信半疑的脚本运行在沙箱时,它们应该只被允许访问来自同一站点的资源,而不是那些来自其它站点可能怀有恶意的资源。
所谓同源是指:域名、协议、端口相同。
另外,同源策略又分为以下两种:
document.write:是直接写入到页面的内容流,如果在写之前没有调用document.open
, 浏览器会自动调用open。
innerHTML:则是DOM页面元素的一个属性,代表该元素的html内容。你可以精确到某一个具体的元素来进行更改。如果想修改document的内容,则需要修改document.documentElement.innerElement
。
使用事件监听 绑定多个事件
- //事件监听 绑定多个事件
- var btn = document.getElementById("btn");
-
- btn.addEventListener("click",hello1);
- btn.addEventListener("click",hello2);
-
- function hello1(){
- alert("hello 1");
- }
- function hello2(){
- alert("hello 2");
- }
- 复制代码
一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象。
类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 length 属性值,代表可接收的参数个数。
常见的类数组转换为数组的方法有以下4种:
- //(1)通过 call 调用数组的 slice 方法来实现转换
- Array.prototype.slice.call(arrayLike);
-
- //(2)通过 call 调用数组的 splice 方法来实现转换
- Array.prototype.splice.call(arrayLike, 0);
-
- //(3)通过 apply 调用数组的 concat 方法来实现转换
- Array.prototype.concat.apply([], arrayLike);
-
- //(4)通过 Array.from 方法来实现转换
- Array.from(arrayLike);
- 复制代码
NaN 指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。
- typeof NaN; // "number"
- 复制代码
NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN !== NaN 为 true。
JavaScript共有七种基本数据类型,分别是 Undefined、Null、Boolean、Number、String、Symbol、BigInt。
进制转换是比较基础的,如果大家熟悉 js 的 API ,那么会首先想到这两个方法:
所以答案就是 (2).toString(2)
首先,map和filter函数的参数,是完全相同的
array.map(function(currentValue,index,arr), thisValue)
array.filter(function(currentValue,index,arr), thisValue)
但是在用途上,它们是有区别的:
示例
- let arr = ["1","2","3"];
- let a = arr.map((item,index,a) =>{
- return item + 1
- });
- console.log(a);//["11", "21", "31"]
- let b = arr.filter((item,index,a) =>{
- return item > 1
- })
- console.log(b);//["2", "3"]
- 复制代码
另外,filter可过滤NaN、null、undefined、0
- let arr = [NaN,null,undefined,"0",0,1,2,3];
- let newArr = arr.filter(item => item);
- console.log(newArr);//["0", 1, 2, 3]
- 复制代码
argments属于类数组对象,转换成数组的方法可以看第八题
首先整个题目考校的是两个函数,和一个字符串转数字的概念
map
函数,接受三个参数,当前值,当前索引,当前数组。- var new_array = arr.map(function callback(currentValue, index, array) {
- // Return element for new_array
- })
- parseInt(string, radix)
- 复制代码
['1','2','3'].map(parseInt)
其实就是等价于下面的代码。 - ['1','2','3'].map((item, index) => {
- return parseInt(item, index)
- })
- // parseInt('1', 0) 1
- // parseInt('2', 1) NaN
- // parseInt('3', 2) NaN
- 复制代码
- function parseIntFun(item) {
- return parseInt(item, 10)
- }
- ['1','2','3'].map(parseIntFun)
- // parseInt('1', 10) 1
- // parseInt('2', 10) 2
- // parseInt('3', 10) 3
- 复制代码
综上所述,返回值是 [1,NaN,NaN]
new.target
属性允许你检测函数或构造方法是否是通过new运算符被调用的。
在通过new运算符被初始化的函数或构造方法中,new.target
返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target
的值是undefined。
我们可以使用它来检测,一个函数是否是作为构造函数通过new被调用的。
- function Foo() {
- if (!new.target) {
- throw "Foo() must be called with new";
- }
- console.log("Foo instantiated with new");
- }
-
- Foo(); // throws "Foo() must be called with new"
- new Foo(); // logs "Foo instantiated with new"
- 复制代码
- for(var i = 0, len = arrayLike.length; i < len; i++) {
- ……
- }
- 复制代码
用 new
创建构造函数的实例时,通常情况下 new
的构造函数后面需要带括号(譬如:new fn()
)。具体的区别要看在什么场景下
有些情况下new
的构造函数后带括号和不带括号的情况一致,譬如:
- function Fn(){
- this.num = 1;
- }
- console.log(new Fn()); //Fn {num:1}
- console.log(new Fn); //Fn {num:1}
- 复制代码
但有些情况下new
的构造函数后带括号和不带括号的情况并不一致,譬如:
- function Fn(){
- this.num = 1;
- }
- console.log(new Fn().num); // 1
- console.log(new Fn.num); // 报错
- 复制代码
结果分析:
从报错信息来看,new Fn.num
执行顺序是这样的:先执行Fn.num
,此时返回结果为undefined
;后执行new
,因new
后面必须跟构造函数,所以new undefined
会报错。
new Fn().num
相当于(new Fn()).num
,所以结果返回1。
从结果来看,new Fn.num
代码相当于new (Fn.num)
,new Fn().num
相当于(new Fn()).num
。由此看来 new
的构造函数后跟括号优先级会提升。
在ECMAScript中,Object
是一个特殊的对象。它本身是一个顶级对象,同时还是一个构造函数,可以通过它(如:new Object()
)来创建一个对象。我们可以认为JavaScript中所有的对象都是Object
的一个实例,对象可以用字面量的方法const obj = {}
声明。
Map是Object的一个子类,可以有序保存任意类型的数据,使用键值对去存储,其中键可以存储任意类型,通过const m = new Map()
即可得到一个map实例。
Cookie过期时间设置为0,表示跟随系统默认,其销毁与Session销毁时间相同,即都在浏览器关闭后的特定时间删除。如果我们写程序的时候不设置Cookie的有效时间,那么,Cookie的有效时间等效于会话时间。
可以
数组是引用类型,const声明的引用类型变量,不可以变的是变量引用始终指向某个对象,不能指向其他对象,但是所指向的某个对象本身是可以变的
- console.log(Array.isArray([])) // true
- console.log(Array.isArray({})) // false
- 复制代码
- console.log([] instanceof Array) // true
- console.log({} instanceof Array) // false
- 复制代码
- console.log([].constructor) // [Function: Array]
- console.log({}.constructor) // [Function: Object]
- 复制代码
- console.log(Object.prototype.toString.call([])) // [object Array]
- console.log(Object.prototype.toString.call({})) // [object Object]
- 复制代码
undefined == null
,结果是true。且它俩与所有其他值比较的结果都是false。String == Boolean
,需要两个操作数同时转为Number。String/Boolean == Number
,需要String/Boolean转为Number。Object == Primitive
,需要Object转为Primitive(具体通过valueOf和toString方法)。- Array.prototype.slice.apply(arguments)
- 复制代码
arguments 为类数组对象,并不是真正的数组。
slice可以实现数组的浅拷贝。
由于 arguments不是真正的数组,所以没有slice方法,通过apply可以调用数组对象的slice方法,从而将arguments 类数组转换为数组。
现代浏览器可以支持用 script 标签引入模块或者脚本,如果要引入模块,必须给 script 标签添加 type=“module”。如果引入脚本,则不需要 type。
当鼠标移动到元素上时就会触发 mouseenter 事件,类似 mouseover,它们两者之间的差别是 mouseenter 不会冒泡。
由于 mouseenter 不支持事件冒泡,导致在一个元素的子元素上进入或离开的时候会触发其 mouseover 和 mouseout 事件,但是却不会触发 mouseenter 和 mouseleave 事件。
offsetWidth/offsetHeight 返回的是元素的布局宽度,它的值包含 content + padding + border 包含了滚动条。
offsetTop 返回的是当前元素相对于其 offsetParent 元素的顶部的距离。
offsetLeft 返回的是当前元素相对于其 offsetParent 元素的左部的距离。
clientTop 返回的是上边框的宽度。
clientLeft 返回的左边框的宽度。
clientWidth/clientHeight 返回的是元素的内部宽度,它的值只包含 content + padding,如果有滚动条,不包含滚动条。
scrollWidth/scrollHeight 返回值包含 content + padding + 溢出内容的尺寸。
scrollTop 属性返回的是一个元素的内容垂直滚动的像素数。
scrollLeft 属性返回的是元素滚动条到元素左边的距离。
Polyfill 指的是用于实现浏览器并不支持的原生 API 的代码。
比如说 querySelectorAll
是很多现代浏览器都支持的原生 Web API,但是有些古老的浏览器并不支持,那么假设有人写了一段代码来实现这个功能使这些浏览器也支持了这个功能,那么这就可以成为一个 Polyfill。
主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop。
setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。
浏览器的JS引擎遇到setTimeout,拿走之后不会立即放入异步队列,同步任务执行之后,timer模块会到设置时间之后放到异步队列中。js引擎发现同步队列中没有要执行的东西了,即运行栈空了就从异步队列中读取,然后放到运行栈中执行。所以setTimeout可能会多了等待线程的时间。
这时setTimeout函数体就变成了运行栈中的执行任务,运行栈空了,再监听异步队列中有没有要执行的任务,如果有就继续执行,如此循环,就叫Event Loop。
不让事件向 document 上蔓延,但是默认事件仍然会执行,当你调用这个方法的时候,如果点击一个连接,这个连接仍然会被打开,
比如在a标签的绑定事件上调用此方法,连接则不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素;
这个方法比较暴力,它会同时阻止事件冒泡也会阻止默认事件;写上此代码,连接不会被打开,事件也不会传递到上一层的父元素;可以理解为return false就等于同时调用了event.stopPropagation()
和event.preventDefault()
事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发生顺序)的问题。
事件冒泡: 微软提出了名为事件冒泡(event bubbling)的事件流。事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象。
因此在事件冒泡的概念下在p元素上发生click事件的顺序应该是p -> div -> body -> html -> document
事件捕获: 网景提出另一种事件流名为事件捕获(event capturing)。与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。
因此在事件捕获的概念下在p元素上发生click事件的顺序应该是document -> html -> body -> div -> p
事件代理(Event Delegation)也称之为事件委托。是JavaScript中绑定事件的常用技巧。
顾名思义,“事件代理”即是把原本需要绑定在子元素的响应事件委托给父元素,让父元素担当事件监听的职务。
事件代理的原理是DOM元素的事件冒泡。
一个事件触发后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。
事件代理的优点:
因为存在浏览器同源策略,所以才会有跨域问题。那么浏览器是出于何种原因会有跨域的限制呢。其实不难想到,跨域限制主要的目的就是为了用户的上网安全。
如果浏览器没有同源策略,会存在什么样的安全问题呢。下面从 DOM 同源策略和 XMLHttpRequest 同源策略来举例说明:
如果没有 DOM 同源策略,也就是说不同域的 iframe 之间可以相互访问,那么黑客可以这样进行攻击:
做一个假网站,里面用 iframe 嵌套一个银行网站 xxxbank.com 把 iframe 宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。这时如果用户输入账号密码,我们的主网站可以跨域访问到 xxxbank.com 的 dom 节点,就可以拿到用户的账户密码了。
如果没有 XMLHttpRequest 同源策略,那么黑客可以进行 CSRF(跨站请求伪造) 攻击:
用户登录了自己的银行页面 xxxbank.com, 该网站向用户的 cookie 中添加用户标识。用户浏览了恶意页面 evil.com, 执行了页面中的恶意 AJAX 请求代码。evil.com 向 xxxbank.com 发起 AJAX HTTP 请求,请求会默认把 xxxbank.com 对应 cookie 也同时发送过去。银行页面从发送的 cookie 中提取用户标识,验证用户无误,response 中返回请求数据。此时数据就泄露了。而且由于 Ajax 在后台执行,用户无法感知这一过程。
因此,有了浏览器同源策略,我们才能更安全的上网。
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它完全独立于语言。它基于JavaScript编程语言,易于理解和生成。
xml 是可扩展标记语言,它定义了一组规则,用于以人类可读和机器可读的格式编码文档。XML的设计目标侧重于Internet上的简单性,通用性和可用性。它是一种文本数据格式,通过Unicode为不同的人类语言提供强大的支持。尽管XML的设计侧重于文档,但该语言被广泛用于表示任意数据结构,例如Web服务中使用的那些数据结构。
以下是JSON和XML之间的一些区别:
1、JSON是一种轻量级的数据交换格式;XML是可扩展标记语言。
2、JSON是基于JavaScript语言;XML源自SGML。
3、JSON是一种表示对象的方式;XML是一种标记语言,使用标记结构来表示数据项。
4、JSON不提供对命名空间的任何支持;XML支持名称空间。
5、JSON支持数组;XML不支持数组。
6、XML的文件相对难以阅读和解释;JSON的文件非常易于阅读。
7、JSON不使用结束标记;XML有开始和结束标签。
8、JSON的安全性较低;XML比JSON更安全。
首先 Undefined
和 null
都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined
代表的含义是未定义,一般变量声明了但还没有定义的时候会返回 undefined.
null
代表的含义是空对象,主要用于赋值给一些可能会返回对象的变量,作为初始化。
当对null
类型使用 typeof 进行判断时,会返回 “object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。
当对undefined
类型使用 typeof 进行判断时,会返回'undefined'
undefined
在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict mode)。顾名思义,这种模式使得Javascript在更严格的条件下运行。
设立"严格模式"的目的,主要有以下几个:
"严格模式"体现了Javascript更合理、更安全、更严谨的发展方向,包括IE 10在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱它。
另一方面,同样的代码,在"严格模式"中,可能会有不一样的运行结果;一些在"正常模式"下可以运行的语句,在"严格模式"下将不能运行。
Babel 是一个 JavaScript 编译器,同时也是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
npm是Node.js的包管理工具,它的诞生也极大的促进了前端的发展,在现代前端开发中都离不开npm的身影。 常见的使用场景有以下几种:
其实在这个语句运行的过程中做了这样几件事情:
- let s = new Object('1');
- s.toString();
- s = null;
- 复制代码
整个过程体现了 基本包装类型
的性质,而基本包装类型恰恰属于基本数据类型,包括Boolean, Number和String。
跨域本质是浏览器基于同源策略的一种安全手段
同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能
所谓同源(即指在同一个域)具有以下三个相同点
反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域
一定要注意跨域是浏览器的限制,你用抓包工具抓取接口数据,是可以看到接口已经把数据返回回来了,只是浏览器的限制,你获取不到数据。用postman请求接口能够请求到数据。这些再次印证了跨域是浏览器的限制。
concat():用于将一个或多个字符串拼接成一个新字符串
slice()、substr()、substring():这三个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。
trim()、trimLeft()、trimRight():删除前、后或前后所有空格符,再返回新的字符串
repeat():接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果
padEnd():复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件
toLowerCase()、 toUpperCase():大小写转化
charAt():返回给定索引位置的字符,由传给方法的整数参数指定
indexOf():从字符串开头去搜索传入的字符串,并返回位置(如果没找到,则返回 -1 )
startWith()、includes():从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值
split:把字符串按照指定的分割符,拆分成数组中的每一项
match:接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp
对象,返回数组
replace:接收两个参数,第一个参数为匹配的内容,第二个参数为替换的元素(可用函数)
面试时能说几个是几个
通过判断 Global 对象是否为 window,如果不为 window,当前脚本没有运行在浏览器中。
- this === window ? 'browser' : 'node';
- 复制代码
函数在运行的时候,会首先创建执行上下文,然后将执行上下文入栈,然后当此执行上下文处于栈顶时,开始运行执行上下文。
在创建执行上下文的过程中会做三件事:创建变量对象,创建作用域链,确定 this 指向,其中创建变量对象的过程中,首先会为 arguments 创建一个属性,值为 arguments,然后会扫描 function 函数声明,创建一个同名属性,值为函数的引用,接着会扫描 var 变量声明,创建一个同名属性,值为 undefined,这就是变量提升。
null不是对象。
虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。
javaScript本地缓存的方式主要有以下四种:
首先,typeof
与instanceof
都是判断数据类型的方法,区别如下:
typeof
会返回一个变量的基本类型,instanceof
返回的是一个布尔值
instanceof
可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型(除非自定义instanceof
方法)
而typeof
也存在弊端,它虽然可以判断基础数据类型(null
除外),但是引用数据类型中,除了function
类型以外,其他的也无法判断
可以看到,上述两种方法都有弊端,并不能满足所有场景的需求
如果需要通用检测数据类型,可以采用Object.prototype.toString
,调用该方法,统一返回格式“[object Xxx]”
的字符串
===
比较, 如果不同, 会进行一次类型转换, 转换成相同类型后再进行比较。比较规则如下:null
和 undefined
相等NaN
则返回 false
null
和 undefined
比较,会返回false
使用场景: 除了在比较对象属性为null
或者undefined
的情况下,我们可以使用相等操作符(==),其他情况建议一律使用全等操作符(===)
length
属性,返回没有指定默认值的参数个数;name
属性,返回该函数的函数名;拓展:箭头函数写法有以下注意点
this
指向外层做作用域new
命令,否则会抛出一个错误arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest
参数代替yield
命令,因此箭头函数不能用作 Generator 函数const obj = { name }
- let lastWord = 'last word';
-
- const a = {
- 'first word': 'hello',
- [lastWord]: 'world'
- };
-
- a['first word'] // "hello"
- a[lastWord] // "world"
- a['last word'] // "world"
- 复制代码
- let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
- x // 1
- y // 2
- z // { a: 3, b: 4 }
- 复制代码
for...in
:循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
Object.keys(obj)
:返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名
Object.getOwnPropertyNames(obj)
:回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名
Object.getOwnPropertySymbols(obj)
:返回一个数组,包含对象自身的所有 Symbol 属性的键名
Reflect.ownKeys(obj)
:返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举
Object.is()
:严格判断两个值是否相等,与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0
不等于-0
,二是NaN
等于自身
Object.assign()
:方法用于对象的合并,将源对象source
(第二个参数)的所有可枚举属性,复制到目标对象target
(第一个参数)
Object.getOwnPropertyDescriptors()
:返回指定对象所有自身属性(非继承属性)的描述对象
Object.setPrototypeOf()
:方法用来设置一个对象的原型对象
Object.getPrototypeOf()
: 用于读取一个对象的原型对象
Object.keys()
: 返回自身的(不含继承的)所有可遍历(enumerable)属性的键名的数组
Object.values()
: 返回自身的(不含继承的)所有可遍历(enumerable)属性的键对应值的数组
Object.entries()
: 返回一个对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对的数组
Object.fromEntries()
: 用于将一个键值对数组转为对象
1. var特点
2. let特点
3. const特点
1、浏览器是从上到下解析HTML的。
2、放在head里的js代码,会在body解析之前被解析;放在body底部的js代码,会在整个页面加载完成之后解析。
head 部分中的脚本: 需调用才执行的脚本或事件触发执行的脚本放在HTML的head部分中。当你把脚本放在head部分中时,可以保证脚本在任何调用之前被加载,
body 部分中的脚本: 当页面被加载时立即执行的脚本放在HTML的body部分。放在body部分的脚本通常被用来生成页面的内容。
脚本会阻塞页面的渲染,所以推荐将其放在 body 底部,因为当解析到 script 标签时,通常页面的大部分内容都已经渲染完成,让用户马上能看到一个非空白页面。
同步:同步是指一个进程在执行某个请求的时候,如果该请求需要一段时间才能返回信息,那么这个进程会一直等待下去,直到收到返回信息才继续执行下去。
异步:异步是指进程不需要一直等待下去,而是继续执行下面的操作,不管其他进程的状态,当有信息返回的时候会通知进程进行处理,这样就可以提高执行的效率了,即异步是我们发出的一个请求,该请求会在后台自动发出并获取数据,然后对数据进行处理,在此过程中,我们可以继续做其他操作,不管它怎么发出请求,不关心它怎么处理数据。
for循环方法:for
、for...of
、for...in
内置方法:some、every、forEach、filter、map、reduce
Web Workers 是在HTML5中出现的,它可以使一个Web应用程序可以在与主执行线程分离的后台线程中运行一个脚本操作。这样做的好处是可以在一个单独的线程中执行费时的处理任务,从而允许主(通常是UI)线程运行而不被阻塞。
它的作用就是给JS创造多线程运行环境,允许主线程创建worker
线程,分配任务给后者,主线程运行的同时worker
线程也在运行,相互不干扰,在worker
线程运行结束后把结果返回给主线程。这样做的好处是主线程可以把计算密集型或高延迟的任务交给worker
线程执行,这样主线程就会变得轻松,不会被阻塞或拖慢。这并不意味着JS语言本身支持了多线程能力,而是浏览器作为宿主环境提供了JS一个多线程运行的环境。
1. JS 动画
优点
css3
动画丰富,有些动画效果,比如曲线运动,冲击闪烁,视差滚动效果,只有js
动画才能完成CSS3
有兼容性问题,而JS
大多时候没有兼容性问题缺点
CSS
动画JavaScript
在浏览器的主线程中运行,而主线程中还有其它需要运行的JavaScript
脚本、样式计算、布局、绘制任务等,对其干扰导致线程可能出现阻塞,从而造成丢帧的情况2. CSS动画
优点
CSS3
可以做到自然降级,而JS
则需要撰写额外代码缺点
CSS
实现稍微复杂一点动画,最后CSS
代码都会变得非常笨重前端常用的动画实现方式有以下种:
transition
属性animation
属性canvas
绘制动画animate
函数css
代码实现会简单一些,js
复杂一些。 复杂动画的话:css
代码就会变得冗长,js
实现起来更优。js
比较灵活,能控制动画暂停,取消,终止等,css
动画不能添加事件,只能设置固定节点进行什么样的过渡动画。css
有浏览器兼容问题,js
大多情况下是没有的。css
动画相对于优一些,css
动画通过GUI
解析,js
动画需要经过js
引擎代码解析,然后再进行 GUI
解析渲染。首先,Cookie、SessionStorage、 LocalStorage都是浏览器的本地存储。它们之前的区别可以从以下几个方面来说:
写入方式:cookie是由服务器端写入的,而SessionStorage、 LocalStorage都是由前端写入的
生命周期:cookie的生命周期是由服务器端在写入的时候就设置好的,LocalStorage是写入就一直存在,除非手动清除,SessionStorage是页面关闭的时候就会自动清除。
存储大小:cookie的存储空间比较小大概4KB,SessionStorage、 LocalStorage存储空间比较大,大概5M。
数据共享:三者数据共享都遵循同源原则,SessionStorage还限制必须是同一个页面。
发送请求是否携带:在前端给后端发送请求的时候会自动携带Cookie中的数据,但是SessionStorage、 LocalStorage不会。
应用场景:Cookie一般用于存储登录验证信息SessionID或者token,LocalStorage常用于存储不易变动的数据,减轻服务器的压力,SessionStorage可以用来检测用户是否是刷新进入页面,如音乐播放器恢复播放进度条的功能。
扩展:从安全性来说,因为每次http请求都会携带cookie信息,这样无形中浪费了带宽,所以cookie应该尽可能少的使用,另外cookie还需要指定作用域,不可以跨域调用,限制比较多。但是用来识别用户登录来说,cookie还是比storage更好用的。其他情况下,尽量使用storage。
storage在存储数据的大小上面秒杀了cookie,现在基本上很少使用cookie了。
localStorage和sessionStorage唯一的差别一个是永久保存在浏览器里面,一个是关闭网页就清除了信息。localStorage可以用来跨页面传递参数,sessionStorage用来保存一些临时的数据,防止用户刷新页面之后丢失了一些参数。
数组不是以一组连续的区域存储在内存中,而是一种哈希映射的形式。它可以通过多种数据结构来实现,其中一种是链表。
js分为基本类型和引用类型:
检测浏览器版本一共有两种方式:
一种是检测 window.navigator.userAgent
的值,但这种方式很不可靠,因为 userAgent
可以被改写,并且早期的浏览器如 ie,会通过伪装自己的 userAgent 的值为 Mozilla 来躲过服务器的检测。
第二种方式是功能检测,根据每个浏览器独有的特性来进行判断,如 ie 下独有的 ActiveXObject
。
Error:Error
是最基本的错误类型,其他的错误类型都继承自该类型。因此,所有错误的类型共享了一组相同的属性。 这个类型的错误很少见。一般使用开发人员自定义抛出的错误。
EvalError:这个错误会在使用eval()
函数发生异常时候抛出。
RangeError:在数值超出相应范围时触发。
ReferenceError:一般出现在变量找不到的情况时触发。
SyntaxError:当Javascript语言解析代码时,Javascript引擎发现了不符合语法规范的tokens或token顺序时抛出SyntaxError。
TypeError:这个错误在JavaScript中是经常遇到的,不管是初学者还是老手。在变量中保存着以外的类型时,或者在访问不存在的方法时。都会导致这种错误。但是归根结底还是由于在执行特定于类型的操作时,变量的类型并不符合要求所致。
URIError:在使用encodeURI或者decodeURI因为URL格式不正确时,就会导致URIError错误。这种错误也很少见。
(1) AJAX
Ajax
即“AsynchronousJavascriptAndXML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。其缺点如下:
(2)Fetch
fetch
号称是AJAX的替代品,是在ES6出现的,使用了ES6中的promise对象。Fetch是基于promise设计的。Fetch的代码结构比起ajax简单多。fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。
fetch的优点:
fetch的缺点:
(3)Axios
Axios
是一种基于Promise封装的HTTP客户端,其特点如下:
for…of
是ES6新增的遍历方式,允许遍历一个含有iterator
接口的数据结构(数组、对象等)并且返回各项的值,和ES3中的for…in
的区别如下:
for…of
遍历获取的是对象的键值,for…in 获取的是对象的键名;for…in
会遍历对象的整个原型链,性能非常差不推荐使用,而 for…of 只遍历当前对象不会遍历原型链;for…in
会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of
只返回数组的下标对应的属性值;总结:for...in
循环主要是为了遍历对象而生,不适用于遍历数组;for...of
循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。
会直接报错。箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。
new操作符的实现步骤如下:
1、创建一个空的简单JavaScript对象(即{});
2、为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;
3、将步骤1新创建的对象作为this的上下文 ;
4、如果该函数没有返回对象,则返回this。
所以,上面的第二、三步,箭头函数都是没有办法执行的。
1. typeof:其中数组、对象、null都会被判断为object,其他判断都正确。
- console.log(typeof 1); // number
- console.log(typeof true); // boolean
- console.log(typeof 'CoderBin'); // string
- console.log(typeof [1,2]); // object
- console.log(typeof function(){}); // function
- console.log(typeof {}); // object
- console.log(typeof undefined); // undefined
- console.log(typeof null); // object
- 复制代码
2. instanceof:可以正确判断对象的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。
- console.log(1 instanceof Number); // false
- console.log(true instanceof Boolean); // false
- console.log('CoderBin' instanceof String); // false
- console.log([1,2,3] instanceof Array); // true
- console.log(function(){} instanceof Function); // true
- console.log({} instanceof Object); // true
- 复制代码
可以看到,instanceof只能正确判断引用数据类型,而不能判断基本数据类型。instanceof 运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
3. constructor
- console.log((1).constructor === Number); // true
- console.log((true).constructor === Boolean); // true
- console.log(('CoderBin').constructor === String); // true
- console.log(([1,2]).constructor === Array); // true
- console.log((function() {}).constructor === Function); // true
- console.log(({}).constructor === Object); // true
- 复制代码
4. Object.prototype.toString.call(): 使用 Object 对象的原型方法 toString
来判断数据类型:
- var test = Object.prototype.toString;
- console.log(test.call(1));
- console.log(test.call(true));
- console.log(test.call('CoderBin'));
- console.log(test.call([1,2,3]));
- console.log(test.call(function(){}));
- console.log(test.call({}));
- console.log(test.call(undefined));
- console.log(test.call(null));
-
- // 结果如下:
- // [object Number]
- // [object Boolean]
- // [object String]
- // [object Array]
- // [object Function]
- // [object Object]
- // [object Undefined]
- // [object Null]
- 复制代码
使用双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。
使用三等号(===)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。
使用 Object.is
来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 是相等的。
函数 isNaN
接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN
的判断。
函数 Number.isNaN
会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。
总结: 和全局函数 isNaN()
相比,Number.isNaN()
不会自行将参数转换成数字,只有在参数是值为 NaN 的数字时,才会返回 true。
Number.isNaN()
方法确定传递的值是否为NaN,并且检查其类型是否为Number。它是原来的全局isNaN()
的更稳妥的版本。
进程和线程(一个进程中可以有多个线程)
进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
进程之间互相独立。线程是不能单独存在的,它是由进程来启动和管理的。
进程和线程的关系
浏览器是多进程的
一般每打开一个Tab页,就相当于创建了一个独立的浏览器进程。但有时会进行进程合并。
use strict
是一种ECMAscript5添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行。
设立"严格模式"的目的,主要有以下几个:
区别:
Srvice worker
是 PWA 的重要组成部分,W3C 组织早在 2014 年 5 月就提出过 Service Worker 这样的一个 HTML5 API,主要用来做持久的离线缓存,也是Web Worker的升级版。
Service worker
(简称 SW) 是一个注册在指定源和路径下的事件驱动 Worker。它采用 JavaScript 控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。你可以完全控制应用在特定情形(最常见的情形是网络不可用)下的表现。
Object.keys()
将对象属性名组成数组,如果 length
为 0
表示空对象JSON.stringify()
将对象转成字符串,如果为 {}
则表示空对象- // 方法 1
- Object.keys(obj).length === 0
-
- // 方法 2
- JSON.stringify(obj) === '{}'
- 复制代码
JSBridge
是给 JavaScript 提供调用 Native 功能的接口,让混合开发中的前端部分可以方便地使用 Native 的功能(例如:地址位置、摄像头)。
实际上,JSBridge 就像其名称中的Bridge的意义一样,是 Native 和非 Native 之间的桥梁,它的核心是构建 Native 和非 Native 间消息通信的通道,而且这个通信的通道是双向的。
- 双向通信的通道: JS 向 Native 发送消息: 调用相关功能、通知 Native 当前 JS 的相关状态等。
- Native 向 JS 发送消息: 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。
结果为 true
在==
(双等于) 中,左右两边都需要转换为数字然后进行比较。
[]
转换为数字为0
。
![]
首先是转换为布尔值,由于[]
作为一个引用类型转换为布尔值为true
, 因此![]
为false
,进而再转换成数字,变为0
。 0 == 0
, 结果为 true
Object.is()
在 ===
(严格等于)的基础上修复了一些特殊情况下的失误,具体来说就是+0和-0,NaN和NaN。
能,但是需要自定义instanceof
行为,例如:
- class PrimitiveString {
- static [Symbol.hasInstance](x) {
- return typeof x === 'string'
- }
- }
- console.log('CoderBin' instanceof PrimitiveString) // true
- 复制代码
这里将原有的instanceof方法重定义,换成了typeof,因此能够判断基本数据类型。
对于原始类型来说,除了判断 null
类型结果是'object'
,其他的可以调用 typeof
方法显示正确的类型。
但对于引用数据类型,除了函数之外,都会显示'object'
。
因此采用typeof
判断对象数据类型是不合适的,采用instanceof
会更好,instanceof
的原理是基于原型链的查询,只要处于原型链中,判断永远为true
BigInt
是一种新的数据类型,用于当整数值大于Number数据类型支持的范围时。这种数据类型允许我们安全地对 大整数 执行算术操作,表示高分辨率的时间戳,使用大整数id,等等,而不需要使用库。
0.1和0.2在转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,此时就已经出现了精度的损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成 0.30000000000000004。
- function foo(arg) {
- bar = "this is a hidden global variable";
- }
- 复制代码
没有使用声明关键字的变量会被当成全局变量
this
创建:- function foo() {
- this.variable = "potential accidental global";
- }
- // foo 调用自己,this 指向了全局对象(window)
- foo();
- 复制代码
上述使用严格模式,可以避免意外的全局变量
- let someResource = getData()
- setInterval(function() {
- let node = document.getElementById('Node')
- if (node) {
- // 处理 node 和 someResource
- node.innerHTML = JSON.stringify(someResource)
- }
- }, 1000)
- 复制代码
如果id
为Node的元素从DOM
中移除,该定时器仍会存在,同时,因为回调函数中包含对someResource
的引用,定时器外面的someResource
也不会被释放
包括我们之前所说的闭包,维持函数内局部变量,使其得不到释放
- function bindEvent() {
- var obj = document.createElement('XXX')
- var unused = function() {
- console.log(obj, '闭包内引用obj obj不会被释放')
- }
- obj = null // 解决方法
- }
- 复制代码
DOM
元素的引用同样造成内存泄露- const refA = document.getElementById('refA')
- document.body.removeChild(refA) // dom删除了
- console.log(refA, 'refA') // 但是还存在引用能console出整个div 没有被回收
- refA = null
- console.log(refA, 'refA') // 解除引用
- 复制代码
包括使用事件监听addEventListener
监听的时候,在不监听的情况下使用removeEventListener
取消对事件监听
BOM
(Browser Object Model),浏览器对象模型,提供了独立于内容与浏览器窗口进行交互的对象
其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退,前进,刷新,浏览器的窗口发生变化,滚动条的滚动,以及获取客户的一些信息如:浏览器品牌版本,屏幕分辨率
常见的BOM对象有:
window
是BOM的核心对象,它表示浏览器的一个实例。在浏览器中,window
对象有双重角色,即是浏览器窗口的一个接口,又是全局对象。因此所有在全局作用域中声明的变量、函数都会变成window
对象的属性和方法
location
对象用于获取或设置窗体的 URL,并且可以用于解析 URL。
navigator
对象主要用来获取浏览器的属性,区分浏览器类型。
screen
对象保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器的信息,比如像素宽度和像素高度
history
对象主要用来操作浏览器URL
的历史记录,可以通过参数向前,向后,或者向指定URL
跳转
正则表达式是一种用来匹配字符串的强有力的方法
它的设计思想是用一种描述性的语言定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的
在 JavaScript
中,正则表达式也是对象,构建正则表达式有两种方式:
- const re = /\d+/g;
- 复制代码
RegExp
对象的构造函数- const re = new RegExp("\\d+","g");
-
- const rul = "\\d+"
- const re1 = new RegExp(rul,"g");
- 复制代码
使用构建函数创建,第一个参数可以是一个变量,遇到特殊字符需要使用\
进行转义
使用场景: 验证手机号码,邮箱,用户名等等需要一定规则的字符就可以使用正则表达式去校验
在JavaScript
中,new
操作符用于创建一个给定构造函数的实例对象
new
关键字主要做了以下的工作:
obj
this
绑定到新建的对象obj
上简单实现:
- // 实现new操作符
- function mynew(Func, ...args) {
- // 1.创建一个新对象
- const obj = {}
- // 2.新对象原型指向构造函数原型对象
- obj.__proto__ = Func.prototype
- // 3.将构建函数的this指向新对象
- let result = Func.apply(obj, args)
- // 4.根据返回值判断
- return result instanceof Object ? result : obj
- }
- 复制代码
测试
- function Person(name, age) {
- this.name = name;
- this.age = age;
- }
- Person.prototype.say = function () {
- console.log(this.name)
- }
-
- let p = mynew(Person, "CoderBin", 18)
- console.log(p) // Person {name: "CoderBin", age: 18}
- p.say() // CoderBin
- 复制代码
函数的 this
关键字在 JavaScript
中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别
在绝大多数情况下,函数的调用方式决定了 this
的值(运行时绑定)
this
关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象
首先先来了解什么是作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合
换句话说,作用域决定了代码区块中变量和其他资源的可见性 我们一般将作用域分成:
全局作用域:任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问
函数作用域:函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问
块级作用域:ES6引入了let
和const
关键字,和var
关键字不同,在大括号中使用let
和const
声明的变量存在于块级作用域中。在大括号之外不能访问这些变量
什么是作用域链:当在Javascript
中使用一个变量的时候,首先Javascript
引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域
如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错
HTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。
优点:说到优点,这里的对比参照物是HTTP协议,概括地说就是:支持双向通信,更灵活,更高效,可扩展性更好。
在forEach
中用return
不会返回,函数会继续执行。 中断方法
使用try
监视代码块,在需要中断的地方抛出异常。
官方推荐方法(替换方法):用every
和some
替代forEach
函数。
every
在碰到return false
的时候,终止循环。some
在碰到return true
的时候,终止循环。call
、apply
、bind
作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this
指向
call
方法的第一个参数是this
的指向,后面传入的是一个参数列表。改变this
指向后原函数会立即执行,且此方法只是临时改变this
指向一次
bind
方法和call很相似,第一参数是this
的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)。改变this
指向后不会立即执行,而是返回一个永久改变this
指向的函数
apply
接受两个参数,第一个参数是this
的指向,第二个参数是函数接受的参数,以数组的形式传入。改变this
指向后原函数会立即执行,且此方法只是临时改变this
指向一次
三者区别总结:
this
对象指向this
要指向的对象,如果没有这个参数或参数为undefined
或null
,则默认指向全局window
apply
是数组,而call
是参数列表,且apply
和call
是一次性传入参数,而bind
可以分为多次传入bind
是返回绑定this之后的函数,apply
、call
则是立即执行JavaScript实现继承有六种方法:原型链继承、盗用构造函数继承、组合继承、原型式继承、寄生式继承、寄生式组合继承
原型: JavaScript` 常被描述为一种基于原型的语言——每个对象拥有一个原型对象
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾
准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype
属性上,而非实例对象本身
原型链: 原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法
在对象实例和它的构造器之间建立一个链接(它是__proto__
属性,是从构造函数的prototype
属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法
使用 navigator.userAgent
,只要里面包含mobi
、android
、iphone
等关键字,就可以认定是移动设备。这种方法的优点是简单方便,缺点是不可靠,因为用户可以修改这个字符串,让手机浏览器伪装成桌面浏览器。
使用 window.screen.width
,如果屏幕宽度小于500像素,就认为是手机。这个方法的缺点在于,如果手机横屏使用,就识别不了。
使用 window.orientation
,侦测屏幕方向,手机屏幕可以随时改变方向(横屏或竖屏),桌面设备做不到。window.orientation
属性用于获取屏幕的当前方向,只有移动设备才有这个属性,桌面设备会返回undefined
。(注意:iPhone 的 Safari 浏览器不支持该属性。)
使用 ontouchstart
事件,手机浏览器的 DOM 元素可以通过ontouchstart
属性,为touch
事件指定监听函数。桌面设备没有这个属性。
使用第三方的工具包,推荐react-device-detect
,它支持多种粒度的设备侦测。
- import {isMobile} from 'react-device-detect';
- if (isMobile) {
- // 当前设备是移动设备
- }
- 复制代码
promise.all
队列中,使用map每一个过滤每一个promise任务,其中任意一个报错后,return一个返回值,确保promise能正常执行走到.then
中。- const p1 = new Promise((resolve, reject) => {
- resolve('p1');
- });
- const p2 = new Promise((resolve, reject) => {
- resolve('p2');
- });
- const p3 = new Promise((resolve, reject) => {
- reject('p3');
- });
-
- Promise.all([p1, p2, p3].map(p => p.catch(e => `出错后返回的值:${e}` )))
- .then(values => {
- console.log(values);
- }).catch(err => {
- console.log(err);
- })
- 复制代码
Promise.allSettled
替代 Promise.all()
。
Promise.allSettled()
方法返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果。
答案:.then会继续执行
虽然Promise是开发过程中使用非常频繁的一个技术点,但是它的一些细节可能很多人都没有去关注过。我们都知道.then
, .catch
, .finally
都可以链式调用,其本质上是因为返回了一个新的Promise实例。
catch的语法形式如下:
- p.catch(onRejected);
- 复制代码
.catch
只会处理rejected
的情况,并且也会返回一个新的Promise
实例。
.catch(onRejected)
与then(undefined, onRejected)
在表现上是一致的。
事实上,catch(onRejected)从内部调用了then(undefined, onRejected)。
.catch(onRejected)
的onRejected
回调中返回了一个状态为rejected
的Promise
实例,那么.catch
返回的Promise
实例的状态也将变成rejected
。.catch(onRejected)
的onRejected
回调中抛出了异常,那么.catch
返回的Promise
实例的状态也将变成rejected
。.catch
返回的Promise
实例的状态将是fulfilled
。在es5中主要是通过构造函数方式和原型方式来定义一个类,在es6中我们可以通过class来定义类。它们的区别有:
es6的class类必须new调用,不能直接执行。
class类不存在变量提升
class类无法遍历它实例原型链上的属性和方法
es6为new命令引入了一个new.target
属性,它会返回new命令作用于的那个构造函数。如果不是通过new调用或Reflect.construct()
调用的,new.target
会返回undefined
class类有static静态方法
概念:前端路由就是把不同路由对应不同的内容或页面的任务交给前端来做,之前是通过服务端根据 url 的不同返回不同的页面实现的。
使用场景:在单页面应用,大部分页面结构不变,只改变部分内容的使用
优缺点:
实现方式:前端路由一共有两种实现方式,一种是通过 hash
的方式,一种是通过使用 pushState
的方式。
在发生触摸动作约300ms之后,移动端会模拟产生click动作,它底下的具有点击特性的元素也会被触发,这种现象称为点击穿透。
常见场景
发生的条件
解决点击穿透的方法
setTimeout
和 setInterval
的运行机制,其实就是将指定的代码移出本次执行,等到下一轮 Event Loop 时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮 Event Loop 时重新判断。
这意味着,setTimeout指定的代码,必须等到本次执行的所有同步代码都执行完,才会执行。
最大的区别:Promise.allSettled
永远不会被reject。
使用Promise.all
时,一旦有一个promise出现了异常,被reject了,尽管能用catch捕获其中的异常,但你会发现其他执行成功的Promise的消息都丢失了。
而Promise.allSettled
不会有这种问题,我们只需专注在then语句里,当有promise被异常打断时,我们依然能妥善处理那些已经成功了的promise,不必全部重来。
JS会在创建变量时自动分配内存,在不使用的时候会自动周期性的释放内存,释放的过程就叫 "垃圾回收"。
一方面自动分配内存减轻了开发者的负担,开发者不用过多的去关注内存使用,但是另一方面,正是因为是自动回收,所以如果不清楚回收的机制,会很容易造成混乱,而混乱就很容易造成"内存泄漏"。
由于是自动回收,所以就存在一个 "内存是否需要被回收的" 的问题,但是这个问题的判定在程序中意味着无法通过某个算法去准确完整的解决,后面探讨的回收机制只能有限的去解决一般的问题。
回收算法:垃圾回收对是否需要回收的问题主要依赖于对变量的判定是否可访问,由此衍生出两种主要的回收算法:
标记清理:标记清理是js最常用的回收策略,2012年后所有浏览器都使用了这种策略,此后的对回收策略的改进也是基于这个策略的改进。其策略是:
局限:
引用计数:引用计数策略相对而言不常用,因为弊端较多。其思路是对每个值记录它被引用的次数,通过最后对次数的判断(引用数为0)来决定是否保留,具体的规则有:
局限:
最重要的问题就是,循环引用的问题
- function foo() {
- let a = new Object()
- let b = new Object()
- a.c = b
- b.c = a //互相引用
- }
- 复制代码
根据之前提到的规则,两个都互相引用了,引用计数不为0,所以两个变量都无法回收。如果频繁的调用改函数,则会造成很严重的内存泄漏。
箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。
可以⽤Babel理解⼀下箭头函数:
- // ES6
- const obj = {
- getArrow() {
- return () => {
- console.log(this === obj)
- }
- }
- }
- 复制代码
转换后:
- // ES5,由 Babel 转译
- var obj = {
- getArrow: function getArrow() {
- var _this = this
- return function() {
- console.log(_this === obj)
- }
- }
- }
- 复制代码
拓展运算符
- let obj = {
- inObj: { a: 1, b: 2 }
- }
- let newObj = { ...obj }
-
- newObj.inObj.a = 2
- console.log(obj) // {inObj: {a: 2, b: 2}}
- 复制代码
使用扩展运算符创建出新的对象,但是执行newObj.inObj.a = 2
后,原对象的里面的值也被改变了
Object.assign()
- let obj = {
- inObj: {a: 1, b: 2}
- }
- let newObj = Object.assign({}, obj)
-
- newObj.inObj.a = 2
- console.log(obj) // {inObj: {a: 2, b: 2}}
- 复制代码
情况和扩展运算符一样,原对象里面的值也被改变了。
所以,两者都是浅拷贝。
扩展操作符(…
)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。
Object.assign()
方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。
我们都知道,页面的内容都是一帧一帧绘制出来的,浏览器刷新率代表浏览器一秒绘制多少帧。原则上说 1s 内绘制的帧数也多,画面表现就也细腻。目前浏览器大多是 60Hz(60帧/s),每一帧耗时也就是在 16.6ms 左右。那么在这一帧的(16.6ms) 过程中浏览器又干了些什么呢?
通过上面这张图可以清楚的知道,浏览器一帧会经过下面这几个过程:
第七步的 RIC 事件不是每一帧结束都会执行,只有在一帧的 16.6ms 中做完了前面 6 件事而且还有剩余时间,才会执行。如果一帧执行结束后还有时间执行 RIC 事件,那么下一帧需要在事件执行结束才能继续渲染,所以 RIC 执行不要超过 30ms,如果长时间不将控制权交还给浏览器,会影响下一帧的渲染,导致页面出现卡顿和事件响应不及时。
在 Vue2.x 的版本中,双向绑定是基于 Object.defineProperty
方式实现的。而 Vue3.x 版本中,使用了 ES6 中的 Proxy
代理的方式实现。
Object.defineProperty
会产生三个主要的问题:Object.keys()
来实现Object.keys()
来实现对每个属性的劫持Object.defineProperty
针对单个属性,这就解决了需要对对象进行深度递归(支持嵌套的复杂对象劫持)实现对每个属性劫持的问题Object.defineProperty
无法劫持数组的问题Object.defineProperty
有更多的拦截方法,对比一些新的浏览器,可能会对 Proxy 针正对性的优化,有助于性能提升Base64编码的思想是是采用64个基本的ASCII码字符对数据进行重新编码。它将需要编码的数据拆分成字节数组。以3个字节为一组。按顺序排列24位数据,再把这24位数据分成4组,即每组6位。再在每组的的最高位前补两个0凑足一个字节。这样就把一个3字节为一组的数据重新编码成了4个字节。当所要编码的数据的字节数不是3的整倍数,也就是说在分组时最后一组不够3个字节。这时在最后一组填充1到2个0字节。并在最后编码完成后在结尾添加1到2个"="。
( 注BASE64字符表:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/)
从以上编码规则可以得知,通过Base64编码,原来的3个字节编码后将成为4个字节,即字节增加了33.3%,数据量相应变大。所以20M的数据通过Base64编码后大小大概为20M*133.3%=26.67M。
虚拟DOM/domDiff
我们常说的虚拟DOM是通过JS对象模拟出来的DOM节点,domDiff是通过特定算法计算出来一次操作所带来的DOM变化。react和vue中都使用了虚拟DOM,我们借着react聊聊虚拟DOM。
react中涉及到虚拟DOM的代码主要分为以下三部分,其中核心是第二步的domDiff算法:
虚拟DOM不一定更快
干前端的都知道DOM操作是性能杀手,因为操作DOM会引起页面的回流或者重绘。相比起来,通过多一些预先计算来减少DOM的操作要划算的多。
但是,“使用虚拟DOM会更快”这句话并不一定适用于所有场景。例如:一个页面就有一个按钮,点击一下,数字加一,那肯定是直接操作DOM更快。使用虚拟DOM无非白白增加了计算量和代码量。即使是复杂情况,浏览器也会对我们的DOM操作进行优化,大部分浏览器会根据我们操作的时间和次数进行批量处理,所以直接操作DOM也未必很慢。
那么为什么现在的框架都使用虚拟DOM呢?因为使用虚拟DOM可以提高代码的性能下限,并极大的优化大量操作DOM时产生的性能损耗。 同时这些框架也保证了,即使在少数虚拟DOM不太给力的场景下,性能也在我们接受的范围内。
而且,我们之所以喜欢react、vue等使用了虚拟DOM框架,不光是因为他们快,还有很多其他更重要的原因。例如react对函数式编程的友好,vue优秀的开发体验等,目前社区也有好多比较这两个框架并打口水战的,我觉着还是在两个都懂的情况下多探究一下原理更有意义一些。
CSS阻塞
css 文件的下载和解析不会影响 DOM 的解析,但是会阻塞 DOM 的渲染。因为 CSSOM Tree 要和 DOM Tree 合成 Render Tree 才能绘制页面。
css 文件没下载并解析完成之前,后续的 js 脚本不能执行。
css 文件的下载不会阻塞前面的 js 脚本执行。(所以在需要提前执行不操作 dom 元素的 js 时,不妨把 js 放到 css 文件之前。)
js阻塞
js 文件的下载和解析会阻塞 GUI 渲染进程,也就是会阻塞 DOM 和 CSS 的解析和渲染。
可枚举性(enumerable)用来控制所描述的属性,是否将被包括在for...in
循环之中(除非属性名是一个Symbol)。具体来说,如果一个属性的enumerable为false,下面三个操作不会取到该属性。
for..in
循环Object.keys
方法JSON.stringify
方法- var o = { a: 1, b: 2 }
-
- o.c = 3
-
- // 添加 d 属性,值为 4,将 enumerable 为设为 false
- Object.defineProperty(o, 'd', {
- value: 4,
- enumerable: false
- })
-
- console.log(o.d);
-
- // 下面的方法取不到 o.d 的值
-
- for (var key in o) console.log(o[key])
- // 1
- // 2
- // 3
-
- console.log(Object.keys(o)) // ["a", "b", "c"]
-
- console.log(JSON.stringify(o)); // => "{a:1,b:2,c:3}"
- 复制代码
上面代码中,d 属性的enumerable
为false
,所以一般的遍历操作都无法获取该属性,使得它有点像“秘密”属性,但还是可以直接获取它的值。
至于for...in
循环和Object.keys
方法的区别,在于前者包括对象继承自原型对象的属性,而后者只包括对象本身的属性。如果需要获取对象自身的所有属性,不管enumerable
的值,可以使用Object.getOwnPropertyNames
方法。
可枚举属性是指那些内部 “可枚举” 标志设置为 true 的属性。对于通过直接的赋值和属性初始化的属性,该标识值默认为即为 true。但是对于通过 Object.defineProperty
等定义的属性,该标识值默认为 false。
js中创建对象的方式一般有两种 Object.create
和 new
- const Base = function() {}
- const o1 = Object.create(Base)
- const o2 = new Base()
- 复制代码
在讲述两者区别之前,我们需要知道:
Foo.prototype
指向了原型对象。constructor
指回构造函数。prototype
属性,所有的对象只有 proto 隐式属性。那这样到底有什么不一样呢?
Object.create
的实现方式- Object.create = function(o) {
- var F = function() {}
- F.prototype = o
- return new F()
- }
- 复制代码
可以看出来。Object.create
是内部定义一个对象,并且让F.prototype
对象 赋值为引进的对象/函数 o,并return
出一个新的对象。
const o2 = new Base()
的时候,new做了什么。- var o1 = new Object();
- o1.[[Prototype]] = Base.prototype;
- Base.call(o1);
- 复制代码
new做法是新建一个obj对象o1,并且让o1的__proto__
指向了Base.prototype
对象。并且使用 call 进行强转作用环境。从而实现了实例的创建。
区别:看似是一样的。我们对原来的代码进行改进一下。
- var Base = function () {
- this.a = 2
- }
- var o1 = new Base();
- var o2 = Object.create(Base);
- console.log(o1.a); // 2
- console.log(o2.a); // undefined
- 复制代码
可以看到Object.create 失去了原来对象的属性的访问。再进行下改造:
- var Base = function() {
- this.a = 2
- }
- Base.prototype.a = 3
- var o1 = new Base()
- var o2 = Object.create(Base)
- console.log(o1.a) // 2
- console.log(o2.a) // undefined
- 复制代码
总结
比较 | new | Object.create |
---|---|---|
构造函数 | 保留原构造函数属性 | 丢失原构造函数属性 |
原型链 | 原构造函数prototype属性 | 原构造函数/(对象)本身 |
作用对象 | function | function和object |
一般来说,URL只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号。
这是因为网络标准RFC 1738做了硬性规定:
"...Only alphanumerics [0-9a-zA-Z], the special characters "$-_.+!*'()," [not including the quotes - ed], and reserved characters used for their reserved purposes may be used unencoded within a URL."
这意味着,如果URL中有汉字,就必须编码后使用。但是麻烦的是,RFC 1738没有规定具体的编码方法,而是交给应用程序(浏览器)自己决定。这导致"URL编码"成为了一个混乱的领域。有没有办法,能够保证客户端只用一种编码方法向服务器发出请求?
就是使用Javascript先对URL编码,然后再向服务器提交,不要给浏览器插手的机会。因为Javascript的输出总是一致的,所以就保证了服务器得到的数据是格式统一的。
语法更加简洁、清晰
箭头函数不会创建自己的this(重点!)
箭头函数不会创建自己的this,所以它没有自己的this,它只会从自己的作用域链的上一层继承this。所以,箭头函数中this的指向在它被定义的时候就已经确定了,之后永远不会改变。
箭头函数继承而来的this指向永远不变(重点!)
.call()/.apply()/.bind()
无法改变箭头函数中this的指向
箭头函数不能作为构造函数使用
箭头函数没有自己的 arguments
。在箭头函数中访问arguments实际上获得的是外层局部(函数)执行环境中的值。
箭头函数没有原型 prototype
箭头函数不能用作 Generator
函数,不能使用 yeild
关键字
Promise
Promise 对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。 这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象
async/await
es2017的新语法,async/await
就是generator + promise的语法糖
async/await
和 Promise 的关系非常的巧妙,await必须在async内使用,并装饰一个Promise对象,async返回的也是一个Promise对象。
async/await
中的return/throw
会代理自己返回的Promise的resolve/reject
,而一个Promise的resolve/reject
会使得await得到返回值或抛出异常。
如果方法内无await节点
{PromiseStatus: resolved}
的Promise。{PromiseStatus: rejected}
的Promise。如果方法内有await节点
async
会返回一个{PromiseStatus: pending}
的Promise(发生切换,异步等待Promise的执行结果)。Promise
的resolve
会使得await
的代码节点获得相应的返回结果,并继续向下执行。Promise
的reject
会使得await
的代码节点自动抛出相应的异常,终止向下继续执行。会被执行。如果不需要执行,需要在 resolve 语句前加上 return。
CSR: 对于html的加载,以React为例,我们习惯的做法是加载js文件中的React代码,去生成页面渲染,同时,js也完成页面交互事件的绑定,这样的一个过程就是CSR(客户端渲染)。
SSR: 但如果这个js文件比较大的话,加载起来就会比较慢,到达页面渲染的时间就会比较长,导致首屏白屏。这时候,SSR(服务端渲染)就出来了:由服务端直接生成html内容返回给浏览器渲染首屏内容。
但是服务端渲染的页面交互能力有限,如果要实现复杂交互,还是要通过引入js文件来辅助实现,我们把页面的展示内容和交互写在一起,让代码执行两次,这种方式就叫同构。
CSR和SSR的区别在于,最终的html代码是从客户端添加的还是从服务端。
内存泄露的解释:程序中己动态分配的堆内存由于某种原因未释放或无法释放。
内存泄漏的几种场景:
常见的攻击方式有:XSS、CSRF、SQL注入
微前端(Micro-Frontends)是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。
各个前端应用还可以独立运行、独立开发、独立部署。
微前端不是单纯的前端框架或者工具,而是一套架构体系,
由于篇幅原因,下半部分内容将
「2022」JavaScript最新高频面试题指南(下)
发布。持续更新中...
每文一句:星星使天空绚烂夺目;知识使人增长才干。
本次的分享就到这里,如果本章内容对你有所帮助的话欢迎点赞+收藏。文章有不对的地方欢迎指出,有任何疑问都可以在评论区留言。希望大家都能够有所收获,大家一起探讨、进步!
地址:前端面试题库