js数据类型

# 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原理:判断实例属于什么类型

// 实现方法
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方法

function unique(arr){
  return Array.from(new Set(arr))
}
// 无法去除 {} 空对象

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

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

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

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

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

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 判断是否存在对象属性

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

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、递归去重

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值,所以最终得到的就是去重后的结果。

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

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属性决定。索引不连续时转换结果是连续的,会自动补位。

数组转树

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

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队列真实包含任务:

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

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

process.nextTick, Promises, Object.observe, MutationObserver

执行顺序:

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

示例:

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