对象类型和原始类型的不同之处?函数参数是对象会发生什么问题?

在 JS 中,除了原始类型那么其他的都是对象类型了。
对象类型和原始类型不同的是:
原始类型存储的是值,对象类型存储的是地址(指针)。
当你创建了一个对象类型的时候,计算机会在内存中帮我们开辟一个空间来存放值,
但是我们需要找到这个空间,这个空间会拥有一个地址(指针)。

一、字符串方法

1
2
3
4
5
6
7
8
9
10
11
12
// 将字符串的字母转换成小写
const str = "Hello world";
arr = str.split("").map(function (item) {
return (item + "").toLowerCase();
});
lowerStr = arr.join("");
console.log(lowerStr);

// 挑出大写字母
const str = "Permission is ";
reg = /[A-Z]/g;
console.log(str.match(reg).join(""));

js 数据类型

基本数据类型:

Number,String,Boolean,null,undefined,symbol,bigint

引用数据类型:

object function

基本数据类型是直接存储在栈中的简单数据段,占据空间小、大小固定,属于被频繁使用的数据。栈是存储基 本类型值和执行代码的空间。

引用数据类型是存储在堆内存中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆 中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。

基本数据类型和引用数据类型的区别:

  1. 堆比栈空间大,栈比堆运行速度快。
  2. 堆内存是无序存储,可以根据引用直接获取。
  3. 基础数据类型比较稳定,而且相对来说占用的内存小。
  4. 引用数据类型大小是动态的,而且是无限的。

Object.assign():合并对象 Object.assign(target, …sources)

  1. Object.assign 会将 source 里面的可枚举属性复制到 target,如果和 target 的已有属性重名,则会覆盖。
  2. 后续的 source 会覆盖前面的 source 的同名属性。
  3. Object.assign 复制的是属性值,如果属性值是一个引用类型,那么复制的其实是引用地址,就会存在引用共享的问题。

Constructor:

创建的每个函数都有一个 prototype(原型)对象,这个属性是一个指针,指向一个对象。在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性是一个指向 prototype 属性所在函数的指针。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(继承自构造函数的 prototype),指向构造函数的原型对象。注意当将构造函数的 prototype 设置为等于一个以对象字面量形式创建的新对象时,constructor 属性不再指向该构造函数。

map 和 forEach 区别

相同点:

  1. 都是循环遍历数组中的每一项
  2. 每次执行匿名函数都支持三个参数,参数分别为 item(当前每一项),index(索引值),arr(原数组)
  3. 匿名函数中的 this 都是指向 window
  4. 只能遍历数组

不同点:

  1. map()会分配内存空间存储新数组并返回,forEach()不会返回数据。
  2. forEach()允许 callback 更改原始数组的元素。map()返回新的数组。

for…of:

es6 新增的一个遍历方法,但只限于迭代器(iterator), 所以普通的对象用 for..of 遍历
是会报错的。

包括 Array, Map, Set, String, TypedArray, arguments 对象

indexOf str.indexOf(searchValue [, fromIndex]) searchValue:要被查找的字符串值。

查找的字符串 searchValue 的第一次出现的索引,如果没有找到,则返回-1。

若被查找的字符串 searchValue 是一个空字符串,则返回 fromIndex。如果 fromIndex 值为空,或者 fromIndex 值小于被查找的字符串的长度,返回值和以下的 fromIndex 值一样。

如果 fromIndex 值大于等于字符串的长度,将会直接返回字符串的长度(str.length)

严格区分大小写

在使用 indexOf 检索数组时,用‘===’去匹配,意味着会检查数据类型

Iframe 的优缺点?

优点:

  1. iframe 能够原封不动的把嵌入的网页展现出来。
  2. 如果有多个网页引用 iframe,那么你只需要修改 iframe 的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
  3. 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用 iframe 来嵌套,可以增加代码的可重用。
  4. 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由 iframe 来解决。

缺点:

  1. iframe 会阻塞主页面的 onload 事件;
  2. iframe 和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。会产生很多页面,不容易管理。
  3. iframe 框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
  4. 代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理 iframe 中的内容,所以使用 iframe 会不利于搜索引擎优化(SEO)。
  5. 很多的移动设备无法完全显示框架,设备兼容性差。
  6. iframe 框架页面会增加服务器的 http 请求,对于大型网站是不可取的。

作用域:

作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6 的到来,为我们提供了‘块级作用域’,可通过新增命令 let 和 const 来体现。

js 的 arguments 相关问题

在 js 中,我们在调用有参数的函数时,当往这个调用的有参函数传参时,js 会把所传的参数全部存到一个叫 arguments 的对象里面。它是一个类数组数据由来

Javascrip 中每个函数都会有一个 Arguments 对象实例 arguments,引用着函数的实参。它是寄生在 js 函数当中的,不能显式创建,arguments 对象只有函数开始时才可用

instanceof 原理:判断实例属于什么类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 实现方法
function new_instance_of(leftVaule, rightVaule) {
let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
while (true) {
if (leftVaule === null) {
return false;
}
if (leftVaule === rightProto) {
return true;
}
leftVaule = leftVaule.__proto__;
}
}

数组去重:

1、es6 Set 方法

1
2
3
4
5
6
7
function unique(arr) {
return Array.from(new Set(arr));
}
// 无法去除 {} 空对象

// 简写
[...new Set(arr)];

2、es5 利用 for 循环嵌套,然后 splice 去重

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
function unique(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) {
//第一个等同于第二个,splice方法删除第二个
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
var arr = [
1,
1,
"true",
"true",
true,
true,
15,
15,
false,
false,
undefined,
undefined,
null,
null,
NaN,
NaN,
"NaN",
0,
0,
"a",
"a",
{},
{},
];
console.log(unique(arr)); //[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}] //NaN和{}没有去重,两个null直接消失了

3、indexOf

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
function unique(arr) {
if (!Array.isArray(arr)) {
console.log("type error!");
return;
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array.indexOf(arr[i]) === -1) {
array.push(arr[i]);
}
}
return array;
}
var arr = [
1,
1,
"true",
"true",
true,
true,
15,
15,
false,
false,
undefined,
undefined,
null,
null,
NaN,
NaN,
"NaN",
0,
0,
"a",
"a",
{},
{},
];
console.log(unique(arr));
// [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}] //NaN、{}没有去重

4、sort

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
function unique(arr) {
if (!Array.isArray(arr)) {
console.log("type error!");
return;
}
arr = arr.sort();
var arrry = [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i - 1]) {
arrry.push(arr[i]);
}
}
return arrry;
}
var arr = [
1,
1,
"true",
"true",
true,
true,
15,
15,
false,
false,
undefined,
undefined,
null,
null,
NaN,
NaN,
"NaN",
0,
0,
"a",
"a",
{},
{},
];
console.log(unique(arr));
// [0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", undefined] //NaN、{}没有去重

5、includes

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
function unique(arr) {
if (!Array.isArray(arr)) {
console.log("type error!");
return;
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (!array.includes(arr[i])) {
//includes 检测数组是否有某个值
array.push(arr[i]);
}
}
return array;
}
var arr = [
1,
1,
"true",
"true",
true,
true,
15,
15,
false,
false,
undefined,
undefined,
null,
null,
NaN,
NaN,
"NaN",
0,
0,
"a",
"a",
{},
{},
];
console.log(unique(arr));
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}] //{}没有去重

6、hasOwnProperty:利用 hasOwnProperty 判断是否存在对象属性

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
function unique(arr) {
var obj = {};
return arr.filter(function (item, index, arr) {
return obj.hasOwnProperty(typeof item + item)
? false
: (obj[typeof item + item] = true);
});
}
var arr = [
1,
1,
"true",
"true",
true,
true,
15,
15,
false,
false,
undefined,
undefined,
null,
null,
NaN,
NaN,
"NaN",
0,
0,
"a",
"a",
{},
{},
];
console.log(unique(arr));
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}] //所有的都去重了

7、filter

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
function unique(arr) {
return arr.filter(function (item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;
});
}
var arr = [
1,
1,
"true",
"true",
true,
true,
15,
15,
false,
false,
undefined,
undefined,
null,
null,
NaN,
NaN,
"NaN",
0,
0,
"a",
"a",
{},
{},
];
console.log(unique(arr));
//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]

8、递归去重

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
42
43
44
45
46
47
function unique(arr) {
var array = arr;
var len = array.length;

array.sort(function (a, b) {
//排序后更加方便去重
return a - b;
});

function loop(index) {
if (index >= 1) {
if (array[index] === array[index - 1]) {
array.splice(index, 1);
}
loop(index - 1); //递归loop,然后数组去重
}
}
loop(len - 1);
return array;
}
var arr = [
1,
1,
"true",
"true",
true,
true,
15,
15,
false,
false,
undefined,
undefined,
null,
null,
NaN,
NaN,
"NaN",
0,
0,
"a",
"a",
{},
{},
];
console.log(unique(arr));
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]

9、Map 数据结构去重

创建一个空 Map 数据结构,遍历需要去重的数组,把数组的每一个元素作为 key 存到 Map 中。由于 Map 中不会出现相同的 key 值,所以最终得到的就是去重后的结果。

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
function arrayNonRepeatfy(arr) {
let map = new Map();
let array = new Array(); // 数组用于返回结果
for (let i = 0; i < arr.length; i++) {
if (map.has(arr[i])) {
// 如果有该key值
map.set(arr[i], true);
} else {
map.set(arr[i], false); // 如果没有该key值
array.push(arr[i]);
}
}
return array;
}
var arr = [
1,
1,
"true",
"true",
true,
true,
15,
15,
false,
false,
undefined,
undefined,
null,
null,
NaN,
NaN,
"NaN",
0,
0,
"a",
"a",
{},
{},
];
console.log(unique(arr));
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]

10、利用 reduce+includes

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
function unique(arr) {
return arr.reduce(
(prev, cur) => (prev.includes(cur) ? prev : [...prev, cur]),
[]
);
}
var arr = [
1,
1,
"true",
"true",
true,
true,
15,
15,
false,
false,
undefined,
undefined,
null,
null,
NaN,
NaN,
"NaN",
0,
0,
"a",
"a",
{},
{},
];
console.log(unique(arr));
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]

null 和 undefined 的区别

undefined 表示一个变量自然的、最原始的状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。所以,在实际使用过程中,为了保证变量所代表的语义,不要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可。

undefined:

  1. 声明了一个变量,但没有赋值
  2. 访问对象上不存在的属性
  3. 函数定义了形参,但没有传递实参
  4. 使用 void 对表达式求值

null:空值

表示 一个对象被人为的重置为空对象,而非一个变量最原始的状态 。 在内存里的表示就是,栈中的变量没有指向堆中的内存对象

typeof null 为什么是 object?

null 有属于自己的类型 Null,而不属于 Object 类型,typeof 之所以会判定为 Object 类型,是因为 JavaScript 数据类型在底层都是以二进制的形式表示的,二进制的前三位为 0 会被 typeof 判断为对象类型,而 null 的二进制位恰好都是 0 ,因此,null 被误判断为 Object 类型。

类数组转换为数组的方法:

  • 使用 Array.from()
  • 使用 Array.prototype.slice.call()
  • 使用 Array.prototype.forEach()进行属性遍历并组成新的数组

// 转换后的数组长度由 length 属性决定。索引不连续时转换结果是连续的,会自动补位。

数组转树

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
function arrayToTree(array) {
let root = array[0];
array.shift();
let tree = {
id: root.id,
val: root.val,
children: array.length > 0 ? toTree(root.id, array) : [],
};
return tree;
}

function toTree(parenId, array) {
let children = [];
let len = array.length;
for (let i = 0; i < len; i++) {
let node = array[i];
if (node.parentId === parenId) {
children.push({
id: node.id,
val: node.val,
children: toTree(node.id, array),
});
}
}
return children;
}

Set、Map、WeakSet 和 WeakMap 的区别

Set

  1. 成员不能重复;
  2. 只有键值,没有键名,有点类似数组;
  3. 可以遍历,方法有 add、delete、has

WeakSet

  1. 成员都是对象(引用);
  2. 成员都是弱引用,随时可以消失(不计入垃圾回收机制)。可以用来保存 DOM 节点,不容易造成内存泄露;
  3. 不能遍历,方法有 add、delete、has;

Map

  1. 本质上是键值对的集合,类似集合;
  2. 可以遍历,方法很多,可以跟各种数据格式转换;

WeakMap

  1. 只接收对象为键名(null 除外),不接受其他类型的值作为键名;
  2. 键名指向的对象,不计入垃圾回收机制;
  3. 不能遍历,方法同 get、set、has、delete;

造成内存泄漏的情况

  1. 意外的全局变量;
  2. 闭包;
  3. 未被清空的定时器;
  4. 未被销毁的事件监听;
  5. DOM 引用;

数据类型的判断方法:

  1. 使用 typeof 检测当需要判断变量是否是 number, string, boolean, function, undefined 等类型时,可以使用 typeof 进行判断。
  2. 使用 instanceof 检测 instanceof 运算符与 typeof 运算符相似,用于识别正在处理的对象的类型。与 typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型。
  3. 使用 constructor 检测 constructor 本来是原型对象上的属性,指向构造函数。但是根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用 constructor 属性的。

什么是 promise 和 async await 以及它们的区别

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大,简单地说,Promise 好比容器,里面存放着一些未来才会执行完毕(异步)的事件的结果,而这些结果一旦生成是无法改变的

async await也是异步编程的一种解决方案,他遵循的是 Generator 函数的语法糖,他拥有内置执行器,不需要额外的调用直接会自动执行并输出结果,它返回的是一个 Promise 对象。

区别:

  1. Promise 的出现解决了传统 callback 函数导致的“地域回调”问题,但它的语法导致了它向纵向发展行成了一个回调链,遇到复杂的业务场景,这样的语法显然也是不美观的。而 async await 代码看起来会简洁些,使得异步代码看起来像同步代码,await 的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。
  2. async await 与 Promise 一样,是非阻塞的。
  3. async await 是基于 Promise 实现的,可以说是改良版的 Promise,它不能用于普通的回调函数。

js 实现 sleep

1
2
3
4
5
6
7
8
9
function sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}

const t1 = +new Date();
sleep(3000).then(() => {
const t2 = +new Date();
console.log(t2 - t1);
});

优点:这种方式实际上是用了 setTimeout,没有形成进程阻塞,不会造成性能和负载问题。

缺点:虽然不像 callback 套那么多层,但仍不怎么美观,而且当我们需要在某过程中需要停止执行(或者在中途返回了错误的值),还必须得层层判断后跳出,非常麻烦,而且这种异步并不是那么彻底,还是看起来别扭

Event Loop执行顺序

  1. 所有任务都在主线程上执行,形成一个执行栈(Execution Context Stack)
  2. 在主线程之外还存在一个任务队列(Task Queen),系统把异步任务放到任务队列中,然后主线程继续执行后续的任务
  3. 一旦执行栈中所有的任务执行完毕,系统就会读取任务队列。如果这时异步任务已结束等待状态,就会从任务队列进入执行栈,恢复执行
  4. 主线程不断重复上面的第三步

宏任务 Macrotask 宏任务是指 Event Loop 在每个阶段执行的任务

微任务 Microtask 微任务是指 Event Loop 在每个阶段之间执行的任务

宏任务队列包含任务: A1, A2 , A3

微任务队列包含任务: B1, B2 , B3

执行顺序为,首先执行宏任务队列开头的任务,也就是 A1 任务,执行完毕后,在执行微任务队列里的所有任务,也就是依次执行 B1, B2 , B3,执行完后清空微任务队中的任务,接着执行宏任务中的第二个任务 A2,依次循环。

宏任务 Macrotask 队列真实包含任务:

1
script(主程序代码),setTimeout, setInterval, setImmediate, I/O, UI rendering

微任务 Microtask 队列真实包含任务:

1
process.nextTick, Promises, Object.observe, MutationObserver

执行顺序:

1
script(主程序代码)—>process.nextTick—>Promises...——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering

示例:

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
console.time("start");

setTimeout(function () {
console.log(2);
}, 10);

setImmediate(function () {
console.log(1);
});

new Promise(function (resolve) {
console.log(3);
resolve();
console.log(4);
}).then(function () {
console.log(5);
console.timeEnd("start");
});

console.log(6);

process.nextTick(function () {
console.log(7);
});

console.log(8);

结果:3——>4——>6——>8——>7——>5——>start: XXXXms——>1——>2