JS基础知识点 1-30

214 阅读18分钟

1. 介绍一下js的数据类型有哪些,值是如何存储的?

包含8种数据类型

  • 7种基本数据类型:Undefined、Null、Boolean、Number、String、Symbol(es6新增)、BigInt(es10新增)
  • 1种引用数据类型:Object(包含Function、Array、Date等)

存储的内存空间不同

  • 基本数据类型:Undefined、Null、Boolean、Number都是按值存储在栈内存中,占据空间小,大小固定,由系统自动分配自动释放
  • 引用数据类型:同时存储在栈和堆中,占据空间大,大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

2. js数据类型的判断?

(1)typeof 对于原始类型来说,除了null都可以显示正确的类型

console.log(typeof 2);               // number
console.log(typeof true);            // boolean
console.log(typeof 'str');           // string
console.log(typeof []);              // object     []数组的数据类型在 typeof 中被解释为 object
console.log(typeof function(){});    // function
console.log(typeof {});              // object
console.log(typeof undefined);       // undefined
console.log(typeof null);            // object     null 的数据类型被 typeof 解释为 object

typeof对于对象来说,除了函数都会显示object。

(2)instanceof

instanceof可以正确判断对象的类型,但是不能精准判断基本数据类型

console.log(2 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false  
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true    
// console.log(undefined instanceof Undefined);
// console.log(null instanceof Null);

MDN解释:instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

(3)constructor

console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true

这里有个坑,如果创建一个对象,并更改它的原型,原有的constructor就会丢失。因此为了规范开发,在重写对象原型是一般都需要重新给constructor赋值,以保证对象实例的类型不被篡改。

function Fn(){};
 
Fn.prototype=new Array();
 
var f=new Fn();
 
console.log(f.constructor===Fn);    // false
console.log(f.constructor===Array); // true 

(4)Object.prototype.toString.call()

  • toString()是Object的原型方法,调用该方法,默认返回当前对象的[[class]]。这是内部属性,其格式为[object Xxx],其中Xxx就是对象的类型。
  • 对于Object对象,直接调用toString()就能返回[object Object]。
  • 而对于其他对象,则需要通过call/apply来调用才返回正确的类型信息。
Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(1) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用

3.介绍js有哪些内置对象?

全局的对象(global objects)或称标准内置对象,不要和“全局对象(global object)”混淆。这里说的全局的对象是说在全局作用域里的对象。【引自:JavsScript标准内置对象

js中的内置对象主要指的是在程序执行前存在全局作用域里的由js定义的一些全局属性、函数和用来实例化其他对象的构造函数对象

标准内置对象的分类

(1)值属性,这些全局属性返回一个简单值,这些值没有自己的属性和方法。

例如 InfinityNaNundefinednull 字面量

(2)函数属性,全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。

例如 eval()、parseFloat()、parseInt() 等

(3)基本对象,基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。

例如 ObjectFunctionBooleanSymbolError 等

(4)数字和日期对象,用来表示数字、日期和执行数学计算的对象。

例如 NumberMathDate5)字符串,用来表示和操作字符串的对象。

例如 StringRegExp6)可索引的集合对象,这些对象表示按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。例如 Array7)使用键的集合对象,这些集合对象在存储数据时会使用到键,支持按照插入顺序来迭代元素。

例如 MapSetWeakMapWeakSet8)矢量集合,SIMD 矢量集合中的数据会被组织为一个数据序列。

例如 SIMD 等

(9)结构化数据,这些对象用来表示和操作结构化的缓冲区数据,或使用 JSON 编码的数据。

例如 JSON 等

(10)控制抽象对象

例如 PromiseGenerator 等

(11)反射

例如 ReflectProxy12)国际化,为了支持多语言处理而加入 ECMAScript 的对象。

例如 IntlIntl.Collator 等

(13WebAssembly14)其他

例如 arguments

4. undefined 与 undeclared 的区别?

  • undefined: 已在作用域中声明但还没有赋值的变量,代表的含义即未定义
  • undeclared: 还没有在作用域中声明过的变量。

对于undeclared变量的引用,浏览器会报引用错误,如ReferenceError: b is not defined, 我们可以使用typeof的安全防范机制来避免报错。

typeof(undeclared) // "undefined"

5.null和undefined的区别?

  • null代表空对象,主要用于赋值给一些可能返回对象的变量,作为初始化。
typeof(null) //"object",这是一个历史遗留问题,其实null不是对象
在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

undefined == null // true
undefined === null // false
  • undefined 在js中不是一个保留字,意味着使用undefined作为变量是很危险的做法,它会影响我们对undefined值的判断。可以通过使用 void 0 来获得安全的undefined值。

6.{}[] 的valueOf和toString的结果是什么?

let obj = {}
obj.valueOf() //{}
obj.toSting() // "[object Object]"

[].valueOf() // []
[].toSting() // ""

7.Javascript 的作用域和作用域链?

详细资料可参考: 「前端料包」深究JavaScript作用域(链)知识点和闭包

8.js创建对象的几种方法?

详细资料可参考:《JavaScript 深入理解之对象创建》

9.js继承的几种实现方式?

详细资料可以参考: 《JavaScript 深入理解之继承》

10. 寄生式组合继承的实现?

//原型式继承:传入一个对象,返回一个原型对象为该对象的新对象。
function object(o){
    function F(){};
    F.prototype = o;
    return new F();
}

//寄生式继承: 创建一个仅用于封装继承过程的函数,该函数在内部以某种方式增强对象,最后返回这个对象。
funtion createAnother(original){
    var clone = object(original); //通过调用函数创建一个新对象
    
    clone.sayHi = funtion(){ // 某种方式增强这个对象
        console.log("hi");
    }
    return clone; //返回这个对象
}

var person = {
    name:"james"
}

var anotherPerson = createAnother(person);

anotherPerson.sayHi(); // "hi"

//寄生式组合继承
function inheritPrototype(subType, superType){
    function F() {};
    //F()的原型指向的是superType
    F.prototype = superType.prototype;
    //subType的原型指向的是F();
    subType.prototype = new F();
    //重新将构造函数指向自己,修正构造函数
    subType.prototype.constructor = subType;
}
// 设置父类
function SuperType(name) {
   this.name = name;
   this.colors = ["red", "blue", "green"];
   SuperType.prototype.sayName = function () {
     console.log(this.name)
   }
}
// 设置子类
function SubType(name, age) {
   //构造函数式继承--子类构造函数中执行父类构造函数
   SuperType.call(this, name);
   this.age = age;
}
// 核心:因为是对父类原型的复制,所以不包含父类的构造函数,也就不会调用两次父类的构造函数造成浪费
inheritPrototype(SubType, SuperType)
// 添加子类私有方法
SubType.prototype.sayAge = function () {
  console.log(this.age);
}
var instance = new SubType("Taec",18)
console.dir(instance)
}

可参考资料:寄生组合式继承

11. 谈谈你对this、call、apply和bind的理解

可参考资料:「前端料包」一文彻底搞懂JavaScript中的this、call、apply和bind

12.JavaScript原型、原型链有什么特点?

参考资料:原型继承

13. JS获取原型的方法?

p.prototype

p.constructor.prototype

Object.getPrototype(p)

14. 什么是闭包?为什么要用它?

创建闭包最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问当前函数的局部变量。

闭包即指:有权访问另一个函数作用域内变量的函数。

闭包有两个常用的用途:

  • 第一是使我们在函数外部能够访问函数内部的变量,可以使用这种方法来创建私有变量。
  • 第二是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
function a(){
    var n = 0;
    function add(){
        n++;
        console.log(n);
    }
    return add;
}
var a1 = a(); //注意,函数名只是一个标识(指向函数的指针),而()才是执行函数。
a1(); //1
a2(); //2 第二次调用n变量还在内存中

面试题:编写一个函数,该函数将遍历整数列表,并在延迟3秒后打印每个元素的索引。

//错误写法:
const arr = [1,2,3,4];
for(var i = 0; i<arr.length;i++){
    setTimeout(function(){
        console.log(i);
    },3000)
}//3秒延迟后,每次打印输出是4,而不是期望的0,1,2,3

原因是因为setTimeout 函数创建了一个可以访问外部其外部作用域的函数(闭包),该作用域是包含索引i的循环

经过3秒后,执行该函数并打印出i的值,该值在循环结束时为4,因为它循环经过0,1,2,3,4并且循环最终停止在4。

正解:

//使用let块级作用域
const arr = [1,2,3,4];
for(let i = 0; i<arr.length;i++){
    setTimeout(function(){
        console.log(i);
    },3000)
}

// 使用闭包
for (var i = 0; i < 5; i++) {
    (function(i) {
        setTimeout(function() {
        console.log(i);
    }, i * 1000);//每间隔1秒打印1,2,3,4
})(i);}

15. 什么是DOM和BOM?

DOM是指文档对象模型,将所有页面内容表示为可以修改的对象。document对象是页面的主要“入口点”,我们可以使用它来更改或创建页面上的任何内容。

BOM指浏览器对象模型,表示由浏览器(主机环境)提供的用于处理文档(document)之外的所有内容的其他对象。

详细资料参考:浏览器环境,规格

16. 三种事件模型是什么?

① DOM0事件模型(原始事件模型)

有两种实现方式:

  • 通过元素属性来绑定事件
  • 先获取页面元素,然后以赋值的形式来绑定事件
const btn = document.getElementById('btn')
btn.onclick = function(){...}
btn.onclick = null //解除事件

DOMO缺点:一个dom节点只能绑定一个事件,再次绑定将会覆盖之前的时间。

② DOM2事件模型

DOM2新增冒泡和捕获的概念,并且支持一个元素节点绑定多个事件。

一次事件共有三个过程:

  • 事件捕获阶段:事件从document一直向下传播到目标元素,依次检查所有节点是否绑定了监听事件,如果有则执行。
  • 事件处理阶段:事件到达目标元素时,触发监听事件。
  • 事件冒泡阶段:事件从目标元素冒泡到document,并且依次检查各个节点是否绑定了监听函数,如果有则执行。

事件绑定的函数是addEventListener(事件名称、事件回调、捕获/冒泡),第三个参数可以指定事件是否在捕获阶段执行。

③ IE事件模型

IE事件只支持冒泡,所以事件流有两个阶段:

  • 事件处理阶段:事件在到达目标元素时,触发监听事件。
  • 事件冒泡阶段:事件从目标元素冒泡到document,并且依次检查各个节点是否绑定了监听函数,如果有则执行。 通过attachEvent(eventType, handler)来添加监听函数,可以添加多个监听函数,会按顺序依次执行。 移除事件:el.detachEvent(eventType,handler)

相关资料:《一个 DOM 元素绑定多个事件时,先执行冒泡还是捕获》

17. 事件委托

事件委托 本质上是利用了浏览器的事件冒泡的机制。

事件冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理。

优点 :

  • 可以不用为每个子元素都绑定一个监听事件,减少了内存的消耗。
  • 可以实现事件的动态绑定,比如新增了一个子节点,我们并不需要单独地为它添加一个监听事件,它所发生的事件会交给父元素中的监听函数来处理。

事件委托也有其局限性:

  • 首先,事件必须冒泡。而有些事件不会冒泡。此外,低级别的处理程序不应该使用event.stopPropagation()
  • 其次,委托可能会增加CPU负载,因为容器级别的处理程序会对容器中任意位置的事件做出反应,而不管我们是否对该事件感兴趣。但是,通常负载可以忽略不计,所以我们不考虑它。

举个简单例子:待办事项列表,用户点击某个列表项时执行某些操作。用js实现此功能。

<ul id="todo-app">
    <li class="item">111</li>
    <li class="item">111</li>
    <li class="item">111</li>
    <li class="item">111</li>
    <li class="item">111</li>
</ul>

下面是事件委托的代码:

document.addEventListener('DOMContentLoaded',function(){
    let app = document.getElementById('todo-app');
    app.addEventListener('click',function(e){
        if(e.target && e.target.nodeName == 'LI'){
            let item = e.target;
            alert('your click on item'+item.innerHTML)
        }
    })
})

将一个事件侦听器绑定到整个容器,然后单击它时能够访问每个列表项,这称为事件委托,这比给每个列表项都单独绑定事件处理程序更高效

相关资料:

事件委托

# DOMContentLoaded 事件

18. 什么是事件传播?

当事件发生在DOM元素上时,该事件并不完全发生在那个元素上。事件传播有三个阶段:

  1. 捕获阶段–事件从 window 开始,然后向下到每个元素,直到到达目标元素事件或event.target。
  2. 目标阶段–事件已达到目标元素。
  3. 冒泡阶段–事件从目标元素冒泡,然后上升到每个元素,直到到达 window。

19. DOM操作 - 怎样添加、移除、移动、复制、创建和查找节点?

(1)创建新节点

  createDocumentFragment()    //创建一个DOM片段
  createElement()   //创建一个具体的元素
  createTextNode()   //创建一个文本节点

(2)添加、移除、替换、插入

appendChild(node)
removeChild(node)
replaceChild(new,old)
insertBefore(new,old)
复制代码

(3)查找

getElementById();
getElementsByName();
getElementsByTagName();
getElementsByClassName();
querySelector();
querySelectorAll();
复制代码

(4)属性操作

getAttribute(key);
setAttribute(key, value);
hasAttribute(key);
removeAttribute(key);

相关资料:

《DOM 概述》

《原生 JavaScript 的 DOM 操作汇总》

《原生 JS 中 DOM 节点相关 API 合集》

20. js数组和字符串有哪些原生方法,列举一下

21. 常用的正则表达式

//(1)匹配 16 进制颜色值
var color = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;

//(2)匹配日期,如 yyyy-mm-dd 格式
var date = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;

//(3)匹配 qq 号
var qq = /^[1-9][0-9]{4,10}$/g;

//(4)手机号码正则
var phone = /^1[34578]\d{9}$/g;

//(5)用户名正则
var username = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;

//(6)Email正则
var email = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;

//(7)身份证号(18位)正则
var cP = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;

//(8)URL正则
var urlP= /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;

// (9)ipv4地址正则
var ipP = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

// (10)//车牌号正则
var cPattern = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/;

// (11)强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):var pwd = /^(?=.\d)(?=.[a-z])(?=.[A-Z]).{8,10}$/

22. js延迟加载的方式有哪些?

  1. 将js脚本放在文档的底部,来使js脚本尽可能的最后来加载执行。
  2. 添加defer属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。 多个设置了defer属性的脚本按规范来说最后是顺序执行的。
  3. 添加async属性, 这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个async属性的脚本的执行顺序是不可预测的。
  4. 动态创建DOM标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建script标签来引入js脚本。

参考资料:[浏览器知识点] 脚本defer、async & CORS

23. 谈谈你对模块化开发的理解?

现在的前端开发,不仅完成浏览的基本需求,通常是一个单页应用,每个视图通过异步的加载方式,这会导致页面初始化和使用过程中会加载越来越多的JavaScript代码,如何在开发环境中组织好这些碎片话的代码和资源(样式、图片、字体、HTML模板等众多资源),并且保证他们在浏览器端快速、优雅加载和更新,就需要一个模块化系统。

由于函数具有独立作用域的特点,最原始的写法是使用函数作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污染,并且模块间没有联系。

后面提出了对象写法,通过将函数作为一个对象的方法来实现,这样解决了直接使用函数作为模块的一些缺点,但是这种办法会暴露所有的模块成员,外部代码可以修改内部属性的值。

现在常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。

相关资料:

《浅谈模块化开发》

JavaScript中的模块

24. js的几种模块规范?

  • 第一种:CommonJS方案,它通过require来引入模块,通过module.exports定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它以同步的方式来引入模块,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。
  • 第二种:AMD方案,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。
  • 第三种:CMD方案,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。
  • 第四种:ES6提出的Module方案,使用import和export的形式来导入和导出模块。

25. AMD和CMD规范的区别?

它们之间的主要区别有两个方面。

  1. 第一个方面是在模块定义时对依赖的处理不同。AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块。而 CMD 推崇就近依赖,只有在用到某个模块的时候再去 require。
  2. 第二个方面是对依赖模块的执行时机处理不同。首先 AMD 和 CMD 对于模块的加载方式都是异步加载,不过它们的区别在于: 模块的执行时机,AMD 在依赖模块加载完成后就直接执行依赖模块,依赖模块的执行顺序和我们书写的顺序不一定一致。而 CMD 在依赖模块加载完成后并不执行,只是下载而已,等到所有的依赖模块都加载好后,进入回调函数逻辑,遇到 require 语句 的时候才执行对应的模块,这样模块的执行顺序就和我们书写的顺序保持一致了。
// CMD
define(function(require, exports, module) {
  var a = require("./a");
  a.doSomething();
  // 此处略去 100 行
  var b = require("./b"); // 依赖可以就近书写
  b.doSomething();
  // ...
});

// AMD 默认推荐
define(["./a", "./b"], function(a, b) {
  // 依赖必须一开始就写好
  a.doSomething();
  // 此处略去 100 行
  b.doSomething();
  // ...
});

相关资料: 《前端模块化,AMD 与 CMD 的区别》

26.ES6模块与CommonJS模块、AMD、CMD的差异。

  1. CommonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用。CommonJS 模块输出的是值的,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。

  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。CommonJS 模块就是对象,即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

27. 谈谈JS的运行机制

  1. 首先js是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。
  2. 在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务
  3. 当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。
  4. 任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。
  5. 当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。

28. arguments的对象是什么?

arguments对象是函数中传递的参数值的集合。它是一个类似数组的对象,因为他有一个length属性,我们可以使用数组索引表示法arguments[1]来访问单个值,但它没有数组中的内置方法,如forEach、reduce、filter和map。

我们可以使用Array.prototype.slicearguments对象转换成一个数组。

function one() { return Array.prototype.slice.call(arguments); }

注意📢:箭头函数中没有arguments对象。

function one() {
  return arguments;
}
const two = function () {
  return arguments;
}
const three = function three() {
  return arguments;
}

const four = () => arguments;

four(); // Throws an error  - arguments is not defined

当我们调用函数four时,它会抛出一个ReferenceError: arguments is not defined error。使用rest语法,可以解决这个问题。

const four = (...args) => args;

这回自动将所有参数值放入数组中。

29.为什么在调用这个函数时,代码中的b会变成一个全局变量?

function myFunc() {
  let a = b = 0;
}

myFunc();

原因是赋值运算符是从右到左的求值的。这意味着当多个赋值运算符出现在一个表达式中时,它们是从右向左求值的。所以上面代码变成了这样:

function myFunc() {
  let a = (b = 0);
}

myFunc();

首先,表达式b = 0求值,在本例中b没有声明。因此,JS引擎在这个函数外创建了一个全局变量b,之后表达式b = 0的返回值为0,并赋给新的局部变量a。

我们可以通过在赋值之前先声明变量来解决这个问题。

function myFunc() {
  let a,b;
  a = b = 0;
}
myFunc();

30. 哪些操作会造成内存泄露?

  1. 意外的全局变量
  2. 被遗忘的计时器或回调函数
  3. 脱离DOM的引用
  4. 闭包
  • 第一种情况是我们由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
  • 第二种情况是我们设置了setInterval定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
  • 第三种情况是我们获取一个DOM元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回收。
  • 第四种情况是不合理的使用闭包,从而导致某些变量一直被留在内存当中。

相关资料:

《JavaScript 内存泄漏教程》

《4 类 JavaScript 内存泄漏及如何避免》

《杜绝 js 中四种内存泄漏类型的发生》

《javascript 典型内存泄漏及 chrome 的排查方法》

参考

由浅入深,66条JavaScript面试知识点