02-面试之前端 js
let、const、var 相关
var:ES5 变量声明方式
- 在变量未赋值时,变量 undefined(未使用声明变量时也为 undefined)
- 作用域:var 的作用域为方法作用域;只要在方法内定义了,整个方法内的定义变量后的代码都可以使用
let:ES6 变量声明方式
- 在变量未声明前直接使用会报错
- 作用域:let 为块作用域,通常 let 比 var 范围要小
- let 禁止重复声明变量,否则会报错;var 可以重复声明
const:ES6 变量声明方式
- const 为常量声明方式;声明变量时必须初始化,在后面出现的代码中不能再修改该常量的值
- const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动
js 数据类型,区别
基本数据类型:
Number,String,Boolean,null,undefined,symbol,bigint
(后两个为 ES6 新增)引用数据类型:
- object,function(proto Function.prototype)
- object:普通对象,数组对象,正则对象,日期对象,Math 数学函数对象。
两种数据存储方式:
- 基本数据类型是直接存储在栈中的简单数据段,占据空间小、大小固定,属于被频繁使用的数据。栈是存储基本类型值和执行代码的空间。
- 引用数据类型是存储在堆内存中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。
两种数据类型的区别:
- 堆比栈空间大,栈比堆运行速度快。
- 堆内存是无序存储,可以根据引用直接获取。
- 基础数据类型比较稳定,而且相对来说占用的内存小。
- 引用数据类型大小是动态的,而且是无限的。
Object.assign 的理解
作用:Object.assign
可以实现对象的合并。
语法:Object.assign(target, ...sources)
解析:
Object.assign
会将 source 里面的可枚举属性复制到 target,如果和 target 的已有属性重名,则会覆盖。- 后续的 source 会覆盖前面的 source 的同名属性。
Object.assign
复制的是属性值,如果属性值是一个引用类型,那么复制的其实是引用地址,就会存在引用共享的问题。
constructor(构造函数)的理解
创建的每个函数都有一个 prototype(原型)对象,这个属性是一个指针,指向一个对象。
在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性是一个指向 prototype 属性所在函数的指针。
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(继承自构造函数的 prototype),指向构造函数的原型对象。注意当将构造函数的 prototype 设置为等于一个以对象字面量形式创建的新对象时,constructor 属性不再指向该构造函数。
map 和 forEach 的区别
相同点:
- 都是循环遍历数组中的每一项
- 每次执行匿名函数都支持三个参数,参数分别为item(当前每一项),index(索引值),arr(原数组)
- 匿名函数中的 this 都是指向 window
- 只能遍历数组
不同点:
map()
会分配内存空间存储新数组并返回,forEach()
不会返回数据。forEach()
允许 callback 更改原始数组的元素。map()
返回新的数组。
for of 可以遍历哪些对象
for..of..
: 它是 ES6 新增的一个遍历方法,但**只限于迭代器(iterator)**, 所以普通的对象用 for..of
遍历是会报错的。
可迭代的对象:包括 Array、Map、Set、String、TypedArray、arguments 对象等等
静态类型检查
js是动态类型语言
静态类型语言 & 动态类型语言
- 静态类型语言:类型检查发生在编译阶段,因此除非修复错误,否则会一直编译失败
- 动态类型语言:只有在程序运行了一次的时候错误才会被发现,也就是在运行时,因此即使代码中包含了会在运行时阻止脚本正常运行的错误类型,这段代码也可以通过编译
js静态类型检查的方法
Flow 是 Facebook 开发和发布的一个开源的静态类型检查库,它允许你逐渐地向 JavaScript 代码中添加类型。
TypeScript 是一个会编译为 JavaScript 的超集(尽管它看起来几乎像一种新的静态类型语言)
使用静态类型的优势
- 可以尽早发现 bug 和错误
- 减少了复杂的错误处理
- 将数据和行为分离
- 减少单元测试的数量
- 提供了领域建模(domain modeling)工具
- 帮助我们消除了一整类bug
- 重构时更有信心
使用静态类型的劣势
- 代码冗长
- 需要花时间去掌握类型
indexof
语法:str.indexOf(searchValue [, fromIndex])
参数:
searchValue:要被查找的字符串值。
如果没有提供确切地提供字符串,
searchValue 会被强制设置为"undefined"
,然后在当前字符串中查找这个值。举个例子:
'undefined'.indexOf()
将会返回 0,因为 undefined 在位置 0 处被找到,但是'undefi'.indexOf()
将会返回 -1 ,因为字符串'undefined'
未被找到fromIndex:可选
数字表示开始查找的位置。可以是任意整数,默认值为 0。
如果 fromIndex 的值小于 0,或者大于
str.length
,那么查找分别从 0 和 str.length 开始。(译者 注:fromIndex 的值小于 0,等同于为空情况;fromIndex 的值大于或等于 str.length,那么结果会直接返回-1。)举个例子:
'hello world'.indexOf('o', -5)
返回4,因为它是从位置 0 处开始查找,然后 o 在位置 4 处被找到。另一方面,'hello world'.indexOf('o', 11)
(或 fromIndex 填入任何大于 11 的值) 将会返回 -1,因为开始查找的位置 11 处,已经是这个字符串的结尾了。
返回值:
查找的字符串 searchValue
的第一次出现的索引,如果没有找到,则返回 -1。
若被查找的字符串 searchValue
是一个空字符串,则返回 fromIndex
。如果 fromIndex 值为空,或者 fromIndex 值小于被查找的字符串的长度,返回值和以下的 fromIndex 值一样。
如果 fromIndex 值大于等于字符串的长度,将会直接返回字符串的长度 str.length
特点:
- 严格区分大小写
- 在使用 indexOf 检索数组时,用
===
去匹配,意味着会检查数据类型。
iframe 有什么优点、缺点
优点:
- iframe 能够原封不动的把嵌入的网页展现出来。
- 如果有多个网页引用 iframe,那么你只需要修改 iframe 的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
- 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用 iframe 来嵌套,可以增加代码的可重用。
- 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由 iframe 来解决。
缺点:
- iframe 会阻塞主页面的 onload 事件;
- iframe 和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。会产生很多页面,不容易管理。
- iframe 框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
- 代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理 iframe 中的内容,所以使用 iframe 会不利于搜索引擎优化(SEO)。
- 很多的移动设备无法完全显示框架,设备兼容性差。
- iframe 框架页面会增加服务器的 http 请求,对于大型网站是不可取的。
Web Components
Web Components 总的来说是提供一整套完善的封装机制来把 Web 组件化这个东西标准化,每个框架实现的组件都统一标准地进行输入输出,这样可以更好推动组件的复用
包含四个部分
- Custom Elements
- HTML Imports
- HTML Templates
- Shadow DOM
Custom Elements
提供一种方式让开发者可以自定义 HTML 元素,包括特定的组成,样式和行为。支持 Web Components 标准的浏览器会提供一系列 API 给开发者用于创建自定义的元素,或者扩展现有元素。
HTML Imports
一种在 HTMLs 中引用以及复用其他的 HTML 文档的方式。这个 Import 很漂亮,可以简单理解为我们常见的模板中的include之类的作用
HTML Templates
模板
Shadow DOM
提供一种更好地组织页面元素的方式,来为日趋复杂的页面应用提供强大支持,避免代码间的相互影响
dva 的数据流流向是怎么样的
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State,所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致。
变量提升
JavaScript 是单线程语言,所以执行肯定是按顺序执行。但是并不是逐行的分析和执行,而是一段一段地分析执行,会先进行编译阶段然后才是执行阶段。在编译阶段阶段,代码真正执行前的几毫秒,会检测到所有的变量和函数声明,所有这些函数和变量声明都被添加到名为 Lexical Environment 的 JavaScript 数据结构内的内存中。所以这些变量和函数能在它们真正被声明之前使用。
作用域
概念:作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6 的到来,为我们提供了‘块级作用域’,可通过新增命令 let 和 const 来体现。
HashMap 和 ArrayMap 有什么区别?
查找效率。
HashMap 因为其根据 hashcode 的值直接算出 index,所以其查找效率是随着数组长度增大而增加的。
ArrayMap 使用的是二分法查找,所以当数组长度每增加一倍时,就需要多进行一次判断,效率下降。扩容数量。
HashMap 初始值 16 个长度,每次扩容的时候,直接申请双倍的数组空间。
ArrayMap 每次扩容的时候,如果 size 长度大于 8 时申请size*1.5
个长度,大于 4 小于 8 时申请 8 个,小于 4 时申请 4 个。这样比较 ArrayMap 其实是申请了更少的内存空间,但是扩容的频率会更高。因此,如果数据量比较大的时候,还是使用 HashMap 更合适,因为其扩容的次数要比 ArrayMap 少很多。扩容效率。
HashMap 每次扩容的时候重新计算每个数组成员的位置,然后放到新的位置。
ArrayMap 则是直接使用System.arraycopy
,所以效率上肯定是 ArrayMap 更占优势。内存消耗。
以 ArrayMap 采用了一种独特的方式,能够重复的利用因为数据扩容而遗留下来的数组空间,方便下一个 ArrayMap 的使用。
而 HashMap 没有这种设计。由于 ArrayMap 之缓存了长度是 4 和 8 的时候,所以如果频繁的使用到 Map,而且数据量都比较小的时候,ArrayMap 无疑是相当的是节省内存的。
总结:综上所述,数据量比较小,并且需要频繁的使用 Map 存储数据的时候,推荐使用 ArrayMap。而数据量比较大的时候,则推荐使用 HashMap。
HashMap 和 Object
Objects 和 Maps 类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。因此(并且也没有其他内建的替代方式了)过去我们一直都把对象当成 Maps 使用。不过 Maps 和 Objects 有一些重要的区别,在下列情况里使用 Map 会是更好的选择:
Map | Object | |
---|---|---|
意外的键 | Map 默认情况不包含任何键。只包含显式插入的键。 | 一个Object有一个原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。注意: 虽然 ES5 开始可以用 Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见。 |
键的类型 | 一个 Map 的键可以是任意值,包括函数、对象或任意基本类型。 | 一个 Object 的键必须是一个 String 或是 Symbol。 |
键的顺序 | Map 中的 key 是有序的。因此,当迭代的时候,一个Map对象以插入的顺序返回键值。 | 一个 Object 的键是无序的注意:自ECMAScript 2015规范以来,对象_确实_保留了字符串和Symbol键的创建顺序; 因此,在只有字符串键的对象上进行迭代将按插入顺序产生键。 |
Size | Map 的键值对个数可以轻易地通过 size 属性获取 | Object的键值对个数只能手动计算 |
迭代 | Map 是 iterable 的,所以可以直接被迭代。 | 迭代一个Object需要以某种方式获取它的键然后才能迭代。 |
性能 | 在频繁增删键值对的场景下表现更好。 | 在频繁添加和删除键值对的场景下未作出优化。 |
javascript 中 arguments 相关的问题
arguments
在js中,我们在调用有参数的函数时,当往这个调用的有参函数传参时,js 会把所传的参数全部存到一个叫 arguments 的对象里面。它是一个类数组数据
由来
Javascrip 中每个函数都会有一个 Arguments 对象实例 arguments,引用着函数的实参。它是寄生在 js 函数当中的,不能显式创建,arguments 对象只有函数开始时才可用
作用
有了 arguments 这个对象之后,我们可以不用给函数预先设定形参了,可以动态地通过 arguments 为函数加入参数
instanceOf 原理,手动实现 function isInstanceOf (child, Parent)
instanceof 主要作用就是判断一个实例是否属于某种类型
1 |
|
instanceOf 原理
1 |
|
其实 instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。
同时还要了解 js 的原型继承原理
我们知道每个 JavaScript 对象均有一个隐式的 proto 原型属性,而显式的原型属性是 prototype,只有 Object.prototype.proto
属性在未修改的情况下为 null 值
手动实现
1 |
|
编码和字符集的区别
参考答案:
字符集是书写系统字母与符号的集合,而字符编码则是将字符映射为一特定的字节或字节序列,是一种规则。通常特定的字符集采用特定的编码方式(即一种字符集对应一种字符编码(例如:ASCII、IOS-8859-1、GB2312、GBK,都是即表示了字符集又表示了对应的字符编码,但Unicode不是,它采用现代的模型))
扩展:
字符:在计算机和电信技术中,一个字符是一个单位的字形、类字形单位或符号的基本信息。即一个字符可以是一个中文汉字、一个英文字母、一个阿拉伯数字、一个标点符号等。
字符集:多个字符的集合。例如 GB2312 是中国国家标准的简体中文字符集,GB2312 收录简化汉字(6763个)及一般符号、序号、数字、拉丁字母、日文假名、希腊字母、俄文字母、汉语拼音符号、汉语注音字母,共 7445 个图形字符。
字符编码:把字符集中的字符编码为(映射)指定集合中的某一对象(例如:比特模式、自然数序列、电脉冲),以便文本在计算机中存储和通过通信网络的传递。
null 和 undefined 的区别,如何让一个属性变为 null
参考答案:
undefined 表示一个变量自然的、最原始的状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。所以,在实际使用过程中,为了保证变量所代表的语义,不要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可。
解析:
undefined 的字面意思就是:未定义的值。这个值的语义是,希望 表示一个变量最原始的状态,而非人为操作的结果。这种原始状态会在以下 4 种场景中出现:
- 声明了一个变量,但没有赋值
- 访问对象上不存在的属性
- 函数定义了形参,但没有传递实参
- 使用 void 对表达式求值
因此,undefined 一般都来自某个表达式最原始的状态值,不是人为操作的结果。当然,你也可以手动给一个变量赋值 undefined,但这样做没有意义,因为一个变量不赋值就是 undefined。
null 的字面意思是:空值。这个值的语义是,希望表示一个对象被人为的重置为空对象,而非一个变量最原始的状态。在内存里的表示就是,栈中的变量没有指向堆中的内存对象
null 有属于自己的类型 Null,而不属于 Object 类型,typeof 之所以会判定为 Object 类型,是因为 JavaScript 数据类型在底层都是以二进制的形式表示的,二进制的前三位为 0 会被 typeof 判断为对象类型,而 null 的二进制位恰好都是 0,因此 null 被误判断为 Object 类型。
数组 和 类数组的区别
定义
- 数组是一个特殊对象,与常规对象的区别:
- 当由新元素添加到列表中时,自动更新 length 属性
- 设置 length 属性,可以截断数组
- 从
Array.protoype
中继承了方法 - 属性为
Array
- 类数组是一个拥有 length 属性,并且他属性为非负整数的普通对象,类数组不能直接调用数组方法。
- 数组是一个特殊对象,与常规对象的区别:
区别
本质:类数组是简单对象,它的原型关系与数组不同。
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 原型关系和原始值转换
let arrayLike = {
length: 10,
};
console.log(arrayLike instanceof Array); // false
console.log(arrayLike.__proto__.constructor === Array); // false
console.log(arrayLike.toString()); // [object Object]
console.log(arrayLike.valueOf()); // {length: 10}
let array = [];
console.log(array instanceof Array); // true
console.log(array.__proto__.constructor === Array); // true
console.log(array.toString()); // ''
console.log(array.valueOf()); // []类数组转换为数组
- 转换方法
- 使用
Array.from()
- 使用
Array.prototype.slice.call()
- 使用
Array.prototype.forEach()
进行属性遍历并组成新的数组
- 使用
- 转换须知
- 转换后的数组长度由 length 属性决定。索引不连续时转换结果是连续的,会自动补位。
代码示例
1
2
3
4
5
6
7
8
9let al1 = {
length: 4,
0: 0,
1: 1,
3: 3,
4: 4,
5: 5,
};
console.log(Array.from(al1)) // [0, 1, undefined, 3]- ②仅考虑 0或正整数 的索引
1
2
3
4
5
6
7
8
9// 代码示例
let al2 = {
length: 4,
'-1': -1,
'0': 0,
a: 'a',
1: 1
};
console.log(Array.from(al2)); // [0, 1, undefined, undefined]- ③使用slice转换产生稀疏数组
1
2
3
4
5
6
7
8
9// 代码示例
let al2 = {
length: 4,
'-1': -1,
'0': 0,
a: 'a',
1: 1
};
console.log(Array.prototype.slice.call(al2)); //[0, 1, empty × 2]- 转换方法
使用数组方法操作类数组注意地方
1
2
3
4
5
6
7
8
9
10
11
12let arrayLike2 = {
2: 3,
3: 4,
length: 2,
push: Array.prototype.push
};
// push 操作的是索引值为 length 的位置
arrayLike2.push(1);
console.log(arrayLike2); // {2: 1, 3: 4, length: 3, push: ƒ}
arrayLike2.push(2);
console.log(arrayLike2); // {2: 1, 3: 2, length: 4, push: ƒ}
介绍下 Set、Map、WeakSet 和 WeakMap 的区别?
Set
- 成员不能重复;
- 只有键值,没有键名,有点类似数组;
- 可以遍历,方法有 add、delete、has
WeakSet
- 成员都是对象(引用);
- 成员都是弱引用,随时可以消失(不计入垃圾回收机制)。可以用来保存 DOM 节点,不容易造成内存泄露;
- 不能遍历,方法有 add、delete、has;
Map
- 本质上是键值对的集合,类似集合;
- 可以遍历,方法很多,可以跟各种数据格式转换;
WeakMap
- 只接收对象为键名(null 除外),不接受其他类型的值作为键名;
- 键名指向的对象,不计入垃圾回收机制;
- 不能遍历,方法同 get、set、has、delete;
简单说说 js 中有哪几种内存泄露的情况
- 意外的全局变量;
- 闭包;
- 未被清空的定时器;
- 未被销毁的事件监听;
- DOM 引用;
json 和 xml 数据的区别
- 数据体积方面:xml 是重量级的,json 是轻量级的,传递的速度更快些。
- 数据传输方面:xml 在传输过程中比较占带宽,json 占带宽少,易于压缩。
- 数据交互方面:json 与 javascript 的交互更加方便,更容易解析处理,更好的进行数据交互。
- 数据描述方面:json 对数据的描述性比 xml 较差。
- xml 和 json 都用在项目交互下,xml 多用于做配置文件,json 用于数据交互。
JavaScript 有几种方法判断变量的类型?
使用
typeof
检测。当需要判断变量是否是 number,string,boolean,function,undefined 等类型时,可以使用 typeof 进行判断。使用
instanceof
检测。instanceof 运算符与 typeof 运算符相似,用于识别正在处理的对象的类型。与 typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型。使用
constructor
检测。constructor 本来是原型对象上的属性,指向构造函数。但是根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用 constructor 属性的。
如何创建响应式布局?
- 可以通过引用 Bootstrap 实现
- 通过看 Bootstrap 源码文件,可知其本质就是通过 CSS 实现的
1 |
|
什么是 ajax 请求?并使用 jQuery 和 XMLHttpRequest 对象实现一个 ajax 请求
没用ajax:浏览器访问服务器请求,用户看得到(页面刷新也就等同于重新发请求,刷新看得到,也就等同于请求看得到)。等请求完,页面刷新,新内容出现,用户看到新内容。
用ajax:浏览器访问服务器请求,用户看不到,是悄悄进行。等请求完,页面不刷新,新内容也会出现,用户看到新内容。
http://www.cnblogs.com/wupeiqi/articles/5703697.html
如何在前端实现轮训?
轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出 HTTP request,然后由服务器返回最新的数据给客户端的浏览器。
1 |
|
如何在前端实现长轮训
ajax 实现:在发送 ajax 后,服务器端会阻塞请求直到有数据传递或超时才返回。
客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
1 |
|
简述 jsonp 及实现原理
因为有同源策略,所以需要使用 scripy 标签
原理:
- 先在客户端注册一个 callback, 然后把 callback 的名字传给服务器。
- 此时,服务器先生成 json 数据。
- 然后以 javascript 语法的方式,生成一个 function , function 名字就是传递上来的参数 jsonp。
- 最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。
- 客户端浏览器,解析 script 标签,并执行返回的 javascript 文档,此时数据作为参数传入到了客户端预先定义好的 callback 函数里.(动态执行回调函数)
注意:
- JSON 是一种数据格式。
- JSONP 是一种数据调用的方式。
什么是 cors
跨域资源共享(Cross-Origin Resource Sharing): 其本质是设置响应头,使得浏览器允许跨域请求。
其实浏览器的请求共分为两类:
简单请求(一次请求)
- 请求方式:HEAD、GET、POST
- 请求头只包括以下信息:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type 对应的值是三个中的任意一个application/x-www-form-urlencoded
、multipart/form-data
、text/plain
非简单请求,又叫复杂请求(两次请求)
除简单请求之外的请求都是非简单请求。非简单请求类请求在发送真正的请求之前,会默认发送一个options
请求,做’预检’,预检成功后才发送真正的请求
预检:如果复杂请求是 PUT 等请求,则服务端需要设置允许某请求,参数为Access-Control-Request-Method
,否则“预检”不通过
如果复杂请求设置了请求头,则服务端需要设置允许某请求头,参数为Access-Control-Request-Headers
,否则“预检”不通过
你曾经使用过哪些前端框架?
- jQuery
- BootStrap、H-ui、layui、Element
- Vue(与vue齐名的前端框架React和Angular)
jQuery this 和 $(this) 的区别
- this 是 javascript 自身的语法关键字,它指向一个 javascript 对象,所以可以使用所指向的目标 javascript 对象所拥有的方法, 但他自己不是一个普通的变量,所以你无法自己定义一个变量叫 this
- 所以为了使用 jQuery 对象的方法,你必须传入 jQuery 函数 $(this), 将 javascript 对象包装成为一个 jquery 对象。