考察很多 new 操作符实现的实现原理 new 操作符的执行过程:
首先创建一个新的空对象
设置原型,将对象的原型设置为函数的 prototype 对象
让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
判断函数的返回值类型,如果是值类型(值类型通常包括基本的数据类型),返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function objectFactory ( ){ let newObject = null ; let constructor = Array .prototype .shift .call (arguments ); let result = null ; if (typeof constructor!== "function" ) { console .error ("type error" ); return ; } newObject = Object .create (constructor.prototype ); result = constructor.apply (newObject,arguments ); let flag = result & (type result === "object" || typeof result === "function" ); result flag ? result : newObject ; } objectFactory (构造函数,初始化参数);
对类数组对象的理解,如何转化为数组? 什么是类数组对象? 一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象。类数组对象的格式通常如下:
1 {0 :value1,1 :value2,2 :value3,length :3 }
类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 length 属性,代表可以接收到参数的个数。常见的类数组转换成数组的方法: (1)通过 call 调用数组的 slice 方法来实现转换
1 Array .prototype .slice .call (arrayLike);
(2)通过 call 调用数组的 splice 方法来实现转换
1 Array .prototype .split .call (arrayLike, 0 );
(3) 通过 apply 调用数组的 concat 方法来实现转换
1 Array .prototype .concat .apply ([], arrayLike);
(4) 通过 Array.from 方法来实现转换
对 Ajax 的理解,实现一个 Ajax 请求 Ajax
指的是通过 js的异步通信
,从服务器获取 XML 文档从中提取数据,在更新到当前网页的对应部分,而不用刷新整个网页。 创建 Ajax 请求的步骤:
创建一个XMLHttpRequest 对象。
在这个对象上使用open 方法创建 HTTP 请求 ,open 方法所需要的参数是请求方法,请求地址,是否为异步和用户的认证信息。
在发起请求前,可以为对象添加一些信息和监听函数 。
当对象的属性和监听函数设置完成后,最后调用sent 方法来向服务器发起请求 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const SERVER_URL = "/server" ;let xhr = new XMLHttpRequest ();xhr.open ("GET" , url, true ); xhr.onreadystatechange = function ( ) { if (this .readyState !== 4 ) return ; if (this .status === 200 ) { handle (this .response ); } else { console .log (this .statusText ); } }; xhr.onerror = function ( ) { console .log (this .statusText ); }; xhr.responseType = "json" ; xhr.setRequestHeader ("Accept" , "application/json" ); xhr.send (null );
使用 Promise 封装 Ajax:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function getJSON (url ) { let promise = new Promise (function (resolve, reject ) { let xhr = new XMLHttpRequest (); xhr.open ("GET" , url, true ); xhr.onreadystatechange = function ( ) { if (this .readyState !== 4 ) return ; if (this .status === 200 ) { resolve (this .response ); } else { reject (new Error (this .statusText )); } }; xhr.onerror = function ( ) { reject (new Error (this .statusText )); }; xhr.responseType = "json" ; xhr.setRequestHeader ("Accept" , "application/json" ); xhr.send (null ); }); return promise; }
JS 为什么要进行变量提升?它有什么问题? 考察较多 map 和 object 的区别: | Map | Object | | ——– | ————————————————————————- | ————————————————————————— | | 意外的键 | Map 默认情况不包含任何键,只包含显示插入的键 | Object 有一个原型,原型链上的键名有可能和自己在对象上的设置的键名产生冲突。 | | 键的类型 | Map 的键可以是任意值,包括函数,对象,或任意基本类型 | Object 的键必须是 String 或是 Symbol | | 键的顺序 | Map 中的 key 是有序的。因此哦,当迭代的时候,Map 对象以插入的顺序返回键值 | object 的键是无序的 | | Size | Map 的键值对个数可以轻易地通过 size 属性获取 | Object 地键值对个数只能手动计算 | | 迭代 | Map 是 iterable 的,所以可以直接被迭代 | 迭代 Object 需要以某种方式获取它的键然后才能迭代 | | 性能 | 在频繁增删键值对的场景下表现更好 | 在频繁增删键值对的场景下未作出优化 |
JS 脚本延迟加载的方式: 延迟加载就是等页面加载完成之后再加载 JS 文件。JS 延迟加载有利于提高页面加载速度,一般有以下几种方式:
defer
属性(脚本的加载与文档解析同步解析,解析后执行)
async
属性(脚本异步加载。加载成功后立即执行)
动态创建 DOM
方式
使用 setTimeout
延迟方式:设置定时器来延迟加载 js 脚本
让 JS 放到文档底部最后加载。
数组有哪些原生方法? 数组和字符串的转换方式:
方法
解释
toString()
将数组转化成字符串
join()
将数组的元素连接成一个字符串,可以指定转换成字符串时的分隔符;
数组添加元素操作方法
方法
解释
返回值
是否改变原数组
push()
向数组末尾添加一个或多个元素
新数组的长度
是
unshift()
向数组的开头添加一个或多个元素
新数组的长度
是
1 2 3 4 5 6 7 8 9 let array = [1 , 2 , 3 ];let length = array.push (4 , 5 );console .log (length); console .log (array); let array1 = [1 , 2 , 3 ];let length1 = array1.unshift (4 , 5 );console .log (array1); console .log (length1);
数组删除元素操作方法
方法
解释
返回值
是否改变原数组
pop()
删除数组的最后一个元素
删除的元素
是
shift()
删除数组的第一个元素
删除的元素
是
1 2 3 4 5 6 7 8 9 let array = [1 , 2 , 3 ];let lastElement = array.pop ();console .log (array); console .log (lastElement); let array1 = [1 , 2 , 3 ];let firstElement = array1.shift ();console .log (array1); console .log (firstElement);
合并数组的操作方法
方法
解释
返回值
是否改变原数组
concat()
将两个或者多个数组合并成一个新的数组
返回合成的新数组
否
1 2 3 4 let array1 = [1 , 2 , 3 ];let array2 = [4 , 5 ];let newArray = array1.concat (array2);console .log (newArray);
数组截取元素操作方法
方法
解释
返回值
是否改变原数组
slice()
用于截取数组的一部分元素,截取元素左闭右开
一个新的数组(截取的目标元素组成的)
否
1 2 3 let array = [1 , 2 , 3 , 4 , 5 ];let newArray = array.slice (1 , 4 );console .log (newArray);
数组插入元素操作方法
方法
解释
返回值
是否改变原数组
splice()
从数组中删除,替换或插入元素。
被删除元素组成的新数组
是
fill()
用指定的值填充数组的元素
splice() 详解
: splice 的使用格式:
1 array.splice(startIndex,deleteCount,item1,item2)
参数详解:
startIndex: 要开始进行修改的索引位置。
deleteCount: 要删除的元素数量。如果设置为 0,则不会删除任何元素
item1,item2…..:要添加到数组的元素
1 2 3 4 let array = [1 , 2 , 3 , 4 , 5 ];let removedElements = array.splice (1 , 2 , 6 , 7 );console .log (array); console .log (removedElements);
对数组排序操作方法
方法
解释
返回值
是否改变原数组
reverse()
反转数组顺序
反转后的数组
是
sort()
对数组进行排序
反转后的数组
是
1 2 3 4 5 6 7 let array = [1 , 2 , 3 , 4 , 5 ];array.reverse (); console .log (array); let array1 = [3 , 1 , 4 , 2 , 5 ];array1.sort (); console .log (array1);
查找特定元素的操作方法
方法
返回值/解释
是否改变原数组
indexOf()
返回指定元素在数组中第一个匹配的位置的索引
否
lastIndexOf()
返回指定元素在数组中最后一个匹配的位置的索引
否
includes()
(解释:)判断数组是否包含指定元素
否
1 2 3 let array = [1 , 2 , 3 , 4 , 5 , 2 ];let index = array.indexOf (2 );console .log (index);
迭代的操作方法
方法
解释
返回值
是否改变原数组
forEach()
对数组的每一个元素执行指定的操作
没有返回值
否
map()
对数组的每一个元素执行指定的操作
一个新的数组
否
filter()
对数组的每个元素进行筛选
返回一个累计结果
否
every()
检测数组的每个元素是否符合指定条件
只要有一个是 false 就返回 false
否
some()
检测数组是否有元素符合指定条件
只要有一个是 true 就返回 true
否
find()
返回数组中满足指定条件的第一个元素
否
findIndex()
返回数组中满足指定条件的第一个元素的索引
否
flatMap()
对数组的每个元素执行指定的操作并将结果压缩成一个新数组
返回一个新数组
数组的其他操作方法
方法
解释
flat()
将多维数组转化成一维数组
isArray()
判断一个对象是否为数组
reduce()
不改变原数组,reduce()对数组正序操作
reduceRight()
不改变原数组,reduceRight()对数组逆序操作
为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组? 前言:如果想要了解什么是类数组,及其具体内容的话,可以参考上方js基础/考察较多/第二条内容。
arguments 是一个对象,它的属性是从 0 开始一次递增的数字,还有 callee 和 length 等属性,与数组类似;但是它却没有数组常见的方法舒总,如 forEach , reduce 等,所以叫他们类数组。 要遍历类数组
有三个方法: (1)将数组的方法应用到类数组上,这个时候就可以使用call 和 apply
方法,如:
1 2 3 function foo ( ) { Array .prototype .forEach .call (argiments, (a ) => console .log (a)); }
(2) 使用 Array.from
方法将类数组转化程数组:
1 2 3 4 function foo ( ) { const arrArgs = Areray .from (arguments ); arrArgs.forEach ((a ) => console .log (a)); }
(3) 使用展开运算符
将类数组转化成数组
1 2 3 4 function foo ( ) { const arrArgs = [...arguments ]; arrArgs.forEach ((a ) => console .log (a)); }
ES6 模块与 ComminJS 模块有什么异同? ES6 Module 和 CommonJS 模块的 区别 :
CommonJS 是对模块的浅拷贝,ES6 Module 是对模块的引用,即 ES6 Module 只存只读,不能改变其值,也就是指针指向不能变,类似 const;
import 的接口是 read-obly(只读状态),不能修改其变量值。即不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对 commonJS 重新赋值(改变指针指向),但是对 ES6 Module 赋值会编译报错。
ES6 Module 和 CommonJS 模块的 共同点 :
CommonJS 和 ES6 Module 都可以对引入的对象进行赋值,即对对象内部属性的值进行改变。
如何判断一个对象是否属于某个类?
使用instanceof 运算符来判断构造函数的 prototypr 属性 是否出现在对象的原型链中的任何位置
第二种方法,通过对象的constructor 属性 来判断,对象的 constructor 属性指向该对象的构造函数,但是这种方式不是很安全,因为 constructor 属性可以被改写
第三种方式,如果需要判断的是某个内置的引用类型的话,可以使用Object.prototype.toString()方法 来打印对象的[[Class]]属性进行判断。
for…in 和 for…of 的区别 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 对象 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const obj = { a : 1 , b : 2 , c : 3 };for (let key in obj) { console .log (key); console .log (obj[key]); } const arr = [1 , 2 , 3 ];for (let value of arr) { console .log (value); } const obj = { a : 1 , b : 2 , c : 3 };const entries = Object .entries (obj);for (let [key, value] of entries) { console .log (key); console .log (value); }
如何使用 for…of 遍历对象 for…of 是作为 ES6 新增的遍历方式,允许遍历一个含有 iterator 接口的数据结构(数组,对象等)并且返回各项的值,但是普通的对象用 for…of 遍历会报错。 如果需要遍历的对象是类数组对象,用 Array.from 转化成数组即可。
1 2 3 4 5 6 7 8 9 var obj = { 0 : "one" , 1 : "two" , length : 2 , }; obj = Array .from (obj); for (let k of obj) { console .log (k); }
如果不是类数组对象,就给对象添加一个[Symbol.iterator]属性,并指向一个迭代器即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 var obj = { a : 1 , b : 2 , c : 3 , }; obj[Symbol .iterator ] = function ( ) { var keys = Object .keys (this ); var count = 0 ; return { next ( ) { if (count < keys.length ) { return { value : obj[keys[count++]], done : false }; } else { return { value : undefined , done : true }; } }, }; }; for (var k of obj) { console .log (k); } var obj = { a : 1 , b : 2 , c : 3 , }; obj[Symbol .iterator ] = function * () { var keys = Object .keys (obj); for (var k of keys) { yield [k, obj[k]]; } }; for (var [k, v] of obj) { console .log (k, v); }
forEach 和 map 方法有什么区别 这方法都是用来遍历数组的,两者区别如下:
forEach()方法会针对每一个元素执行提供的函数,对数据的操作会改变原数组,该方法没有返回值;
map()方法不会改变原数组的值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值。
考察较少 常用的正则表达式有哪些? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var regex = /#([0-9a-fA-F]{6}|([0-9a-fA-F]{3})/g ;var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][1-9]|3[01])$/ ;var regex = /^1[345678]\d{9}$/g ;var regex = /^1[34578]\d{9}$/g ;var regex = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/ ;
正则常用符号总结:
符号
含义
例子
[ ]
里面的内容表示或运算
[ab]==a|b
[a-z] : 任意小写字母
[^ ]
表示除了
.
表示任意字符
\
转义字符
\.表示.
\w
任意字母,数字
等价于[A-z0-9]
\W
除了字母,数字
等价于[^A-z0-9]
\d
任意数字
等价于[0-9]
\D
除了数字
等价于[^0-9]
\s
空格
\S
除了空格
\b
单词边界
\B
除了单词边界
正则量词表示:
量词
描述
n+
匹配任何包含至少一个 n 的字符串
n*
匹配任何包含零个或多个 n 的字符串
n?
匹配任何包含零个或一个 n 的字符串
n{x}
匹配任何包含 x 个 n 的序列的字符串
n{x,y}
匹配任何包含 x 个或 y 个 n 的序列的字符串
n{x,}
匹配任何至少包含 x 个 n 的序列的字符串
n$
匹配任何结尾为 n 的字符串
^n
匹配任何开头为 n 的字符串
?=n
匹配任何其后紧接指定字符串为 n 的字符串
?!n
匹配任何其后没有紧接指定字符串为 n 的字符串
支持正则表达式的 String 对象的常用方法:
方法
描述
search
检索与正则表达式相匹配的值
match
找到一个或多个正则表达式的匹配
replace
替换与正则表达式匹配的子串
split
把字符串分割为字符串数组
2.对 JSON 的理解 JSON 是一种基于文本的轻量级的数据交换格式。它可以被任何的编程语言读取和作为数据格式来传递。 因此,在项目开发过程中使用 JSON 作为前后端数据交换的方式。在前端通过将一个符合 JSON 格式的数据结构序列转化为 JSON 的字符串,然后将它传递到后端,后端通过 JSON 格式的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。 因为 JSON 的语法是基于 js 的,因此很容易将 JSON 和 js 中的对象弄混,但是应该注意的是 JSON 和 js 中的对象不是一回事,JSON 中对象格式更加严格,比如说在 JSON 中属性值不能为函数,不能出现 NaN 这样的属性值等,因此大多数的 js 对象是不符合 JSON 对象的格式的。 在 js 中提供了两个函数来实现 js 数据结构和 JSON 格式的转换处理:
JSON.stringify()函数 ,通过传入一个符合 JSON 格式的数据结构,将其转换为一个 JSON 字符串。如果传入的数据结构不符合 JSON 格式,那么在序列化的时候会对这些值进行对应的特殊处理,使其符合规范。 在前端向后端发送数据时 ,可以调用这个函数将数据对象转化为 JSON 格式的字符串。
JSON.parse()函数 ,这个函数用来将 JSON 格式的字符串转换为一个 js 数据结构,如果传入的字符串不是标准的 JSON 格式的字符串的话,将会抛出错误。当 从后端接收到 JSON 格式的字符串时 ,可以通过这个方法来将其解析为一个 js 数据结构,以此来进行数据的访问。
什么是尾调用,使用尾调用有什么好处? 尾调用指的是函数的最后异步调用另一个函数。代码执行是基于执行栈的,所以当一个函数里调用另一个函数时,会保留当前执行的上下文,然后再新建另一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步了,所以这时可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式下无效 。
常见的 DOM 操作有哪些?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 getElementById; getElementsByTagName; getElementsByClassName; querySelectorAll; var imooc = document .getElementById ("imooc" ); var pList = document .getElementsByTagName ("p" ); console .log (divList.length );console .log (divList[0 ]);var moocList = document .getElementsByClassName ("mooc" ); var pList = document .querySelectorAll (".mooc" );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <html > <head > <title > DEMO</title > </head > <body > <div id ="container" > <h1 id ="title" > 我是标题</h1 > </div > <script > var container = document .getElementById ("container" ); var targetSpan = document .createElement ("span" ); targetSpan.innerHTML = "hello world" ; container.appendChild (targetSpan); </script > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <html > <head > <title > DEMO</title > </head > <body > <div id ="container" > <h1 id ="title" > 我是标题</h1 > </div > <script > var container = document .getElementById ("container" ); var targetNode = document .getElementById ("title" ); container.removeChild (targetNode); </script > </body > </html >
指定交换两个 DOM 元素的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <html > <head > <title > DEMO</title > </head > <body > <div id ="container" > <h1 id ="title" > 我是标题</h1 > <p id ="content" > 我是内容</p > </div > <script > var container = document .getElementById ("container" ); var title = document .getElementById ("title" ); var content = document .getElementById ("content" ); container.insertBefore (content, title); </script > </body > </html >
使用 classList 属性的 add()和 remove()方法向元素添加或移除 CSS 类。
1 2 element.classList .add ("newClass" ); element.classList .remove ("oldClass" );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 element.innerHTML = "New content" ; element.textContent = "New text" ; const value = element.getAttribute ("src" );element.setAttribute ("src" , "newImage.jpg" ); element.addEventListener ("click" , handleClick);
强类型语言和弱类型语言的区别?
强类型语言 : 强类型语言也称为强类型定义语言,是一种总是强制类型定义的语言,要求变量使用要严格符合定义,所有变量都必须先定义后使用。 Java 和 C++等语言都是强制类型定义的 ,也就是说,一旦一个变量被指定了某一个数据类型,如果不经过强制类型转换,那么它就永远是这个数据类型。例如:我们定义了一个整数,如果不显式地进行转换,那么这个整数就不能被视为是一个字符串。
弱类型语言 : 弱类型语言也称为弱类型定义语言,与强类型定义相反。JS 语言就属于弱类型语言。简单理解就是一种变量类型可以被忽略地语言。比如 JS 是弱类型定义的 ,在 JS 中就可以将字符串”12”和整数 3 进行连接得到字符串’123’,在相加的时候会进行强制类型转换。
区别 :
两者对比:强类型语言在速度上可能略逊于弱类型语言,但是强类型语带来的严谨性可以有效地避免很多错误。
ajax、axios、fetch 的区别 (1)AJAX Ajax 即“AsynchronousJavascriptAndXML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。其缺点 如下:
本身是针对 MVC 编程,不符合前端 MVVM 的浪潮
基于原生 XHR 开发,XHR 本身的架构不清晰
不符合关注分离(Separation of Concerns)的原则
配置和调用方式非常混乱,而且基于事件的异步模型不友好。
(2)Fetch fetch 号称是 AJAX 的替代品,是在 ES6 出现的,使用了 ES6 中的 promise 对象。Fetch 是基于 promise 设计的。Fetch 的代码结构比起 ajax 简单多。fetch 不是 ajax 的进一步封装,而是原生 js,没有使用 XMLHttpRequest 对象。fetch 的优点:
语法简洁,更加语义化
基于标准 Promise 实现,支持 async/await
更加底层,提供的 API 丰富(request, response)
脱离了 XHR,是 ES 规范里新的实现方式
fetch 的缺点:
fetch 只对网络请求报错,对 400,500 都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
fetch 默认不会带 cookie,需要添加配置项: fetch(url, {credentials: ‘include’}) fetch 不支持 abort,不支持超时控制,使用 setTimeout 及 Promise.reject 的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
fetch 没有办法原生监测请求的进度,而 XHR 可以
(3)Axios Axios 是一种 基于 Promise 封装的 HTTP 客户端,其特点如下:
浏览器端发起 XMLHttpRequests 请求
node 端发起 http 请求
支持 Promise API
监听请求和返回
对请求和返回进行转化
取消请求
自动转换 json 数据
客户端支持抵御 XSRF 攻击