前端面试之JavaScript总结(作业帮一面)

0.javascript数据类型

变量有以下数据类型:

  • 值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。
  • 引用数据类型:对象(Object)、数组(Array)、函数(Function)。

堆内存和栈内存

  • JS中,所有的变量都是保存在栈内存中的。

  • 基本数类型:值类型的值,直接保存在栈内存中。值与值之间是独立存在,修改一个变量不会影响其他的变量。

  • 引用数据类型:对象是保存到堆内存中的。每创建一个新的对象,就会在堆内存中开辟出一个新的空间,而变量保存了对象的内存地址(对象的引用)。如果两个变量保存了同一个对象的引用,当一个通过一个变量修改属性时,另一个也会受到影响。

1.数组方法

数组的四个基本方法如下

方法描述备注
push()向数组的最后面插入一个或多个元素,返回结果为该数组新的长度会改变原数组
pop()删除数组中的最后一个元素,返回结果为被删除的元素会改变原数组
unshift()在数组最前面插入一个或多个元素,返回结果为该数组新的长度会改变原数组
shift()删除数组中的第一个元素,返回结果为被删除的元素会改变原数组

遍历数组的方法如下

方法描述备注
for循环这个大家都懂
forEach()和 for循环类似,但需要兼容IE8以上forEach() 没有返回值。也就是说,它的返回值是 undefined
filter()返回结果是true的项,将组成新的数组。可以起到过滤的作用不会改变原数组
map()对原数组中的每一项进行加工
every()如果有一项返回false,则停止遍历意思是,要求每一项都返回true,最终的结果才返回true
some()只要有一项返回true,则停止遍历

数组的常见方法如下:

方法描述备注
slice()从数组中提取指定的一个或多个元素,返回结果为新的数组不会改变原数组
splice()从数组中删除指定的一个或多个元素,返回结果为新的数组会改变原数组
concat()连接两个或多个数组,返回结果为新的数组不会改变原数组
join()将数组转换为字符串,返回结果为转换后的字符串不会改变原数组
reverse()反转数组,返回结果为反转后的数组会改变原数组
sort()对数组的元素,默认按照Unicode编码,从小到大进行排序会改变原数组

数组的其他方法如下:

方法描述备注
indexOf(value)从前往后索引,获取 value 在数组中的第一个下标
lastIndexOf(value)从后往前索引,获取 value 在数组中的最后一个下标
find(function())找出第一个满足「指定条件返回true」的元素。
findIndex(function())找出第一个满足「指定条件返回true」的元素的index
Array.from(arrayLike)伪数组转化为真数组
Array.of(value1, value2, value3)一系列值转换成数组。
isArray(obj)用于判断一个对象是否为数组。
toString()把数组转换成字符串,每一项用,分割
valueOf()返回数组本身
includes(searchElement, fromIndex)方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false。

补充知识

  • 数组的set集合内部无重复,(只需要将最后转化为数组即可array.from)
  • reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

数组去重

0.无数组方法原生

//    编写一个方法 去掉一个数组的重复元素
    var arr = [1,2,3,4,5,2,3,4];
    console.log(arr);
    var aaa = fn(arr);
    console.log(aaa);
    //思路:创建一个新数组,循环遍历,只要新数组中有老数组的值,就不用再添加了。
    function fn(array){
        var newArr = [];
        for(var i=0;i<array.length;i++){
            //开闭原则
            var bool = true;
            //每次都要判断新数组中是否有旧数组中的值。
            for(var j=0;j<newArr.length;j++){
                if(array[i] === newArr[j]){
                    bool = false;
                }
            }
            if(bool){
                newArr[newArr.length] = array[i];
            }
        }
        return newArr;
    }

一、利用ES6 Set去重(ES6中最常用)

function unique (arr) {
  return Array.from(new Set(arr))
}

不考虑兼容性,这种去重的方法代码最少。这种方法还无法去掉“{}”空对象,后面的高阶方法会添加去掉重复“{}”的方法。

二、利用for嵌套for,然后splice去重(ES5中最常用)

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;
}
 //NaN和{}没有去重,两个null直接消失了

双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。
想快速学习更多常用的ES6语法,可以看我之前的文章《学习ES6笔记──工作中常用到的ES6语法》

三、利用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;
}
 //NaN、{}没有去重

新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组。

四、利用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;
}
 //NaN、{}没有去重

利用sort()排序方法,然后根据排序后的结果进行遍历及相邻元素比对。

五、利用对象的属性不能相同的特点进行去重(这种数组去重的方法有问题,不建议用,有待改进)

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var arrry= [];
     var  obj = {};
    for (var i = 0; i < arr.length; i++) {
        if (!obj[arr[i]]) {
            arry.push(arr[i])
            obj[arr[i]] = 1
        } else {
            obj[arr[i]]++
        }
    }
    return arrry;
}
    //两个true直接去掉了,NaN和{}去重

六、利用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
}
//{}没有去重

七、利用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)
    })
}
   //所有的都去重了

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

八、利用filter

function unique(arr) {
  return arr.filter(function(item, index, arr) {
    //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
    return arr.indexOf(item, 0) === index;
  });
}

九、利用递归去重

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;
}
 

十、利用Map数据结构去重

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 ;
}
 

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

十一、利用reduce+includes

function unique(arr){
    return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[]);
}

十二、[…new Set(arr)]

[...new Set(arr)] 
//代码就是这么少----(其实,严格来说并不算是一种,相对于第一种方法来说只是简化了代码)

2.原型链

原型链是面向对象的基础,是非常重要的部分。有以下几种知识:

  • 创建对象有几种方法
  • 原型、构造函数、实例、原型链
  • instanceof的原理
  • new 运算符

创建对象有几种方法

方式1:字面量
    var obj11 = {name: 'qianguyihao'};
    var obj12 = new Object(name: 'qianguyihao'); //内置对象(内置的构造函数)

上面的两种写法,效果是一样的。因为,第一种写法,obj11会指向Object

  • 第一种写法是:字面量的方式。
  • 第二种写法是:内置的构造函数
方式2:通过构造函数
    var M = function (name) {
        this.name = name;
    }
    var obj3 = new M('smyhvae');
方法3:Object.create
    var p = {name:'smyhvae'};
    var obj3 = Object.create(p);  //此方法创建的对象,是用原型链连接的

第三种方法,很少有人能说出来。这种方式里,obj3是实例,p是obj3的原型(name是p原型里的属性),构造函数是Objecet

原型、构造函数、实例,以及原型链

PS:任何一个函数,如果在前面加了new,那就是构造函数。

原型、构造函数、实例三者之间的关系

  • 1、构造函数通过 new 生成实例
  • 2、构造函数也是函数,构造函数的prototype指向原型。(所有的函数有prototype属性,但实例没有 prototype属性)
  • 3、原型对象中有 constructor,指向该原型的构造函数。

上面的三行,代码演示:

    var Foo = function (name) {
        this.name = name;
    }

    var foo = new Foo('smyhvae');

上面的代码中,Foo.prototype.constructor === Foo的结果是true

  • 4、实例的__proto__指向原型。也就是说,foo.__proto__ === Foo.prototype

声明:所有的引用类型(数组、对象、函数)都有__proto__这个属性。

Foo.__proto__ === Function.prototype的结果为true,说明Foo这个普通的函数,是Function构造函数的一个实例。

原型链

原型链的基本原理:任何一个实例,通过原型链,找到它上面的原型,该原型对象中的方法和属性,可以被所有的原型实例共享。

Object是原型链的顶端。

原型可以起到继承的作用。原型里的方法都可以被不同的实例共享:

    //给Foo的原型添加 say 函数
    Foo.prototype.say = function () {
        console.log('');
    }

原型链的关键:在访问一个实例的时候,如果实例本身没找到此方法或属性,就往原型上找。如果还是找不到,继续往上一级的原型上找。

instanceof的原理

instanceof作用:用于判断实例属于哪个构造函数

instanceof原理:判断实例对象的__proto__属性,和构造函数的prototype属性,是否为同一个引用(是否指向同一个地址)。

注意1:虽然说,实例是由构造函数 new 出来的,但是实例的__proto__属性引用的是构造函数的prototype。也就是说,实例的__proto__属性与构造函数本身无关。

注意2:在原型链上,原型的上面可能还会有原型,以此类推往上走,继续找__proto__属性。这条链上如果能找到, instanceof 的返回结果也是 true。

比如说:

  • foo instance of Foo的结果为true,因为foo.__proto__ === Foo.prototype为true。
  • foo instance of Objecet的结果也为true,因为Foo.prototype.__proto__ === Object.prototype为true。

但我们不能轻易的说:foo 一定是 由Object创建的实例。这句话是错误的。我们来看下一个问题就明白了。

例题

问题:已知A继承了B,B继承了C。怎么判断 a 是由A直接生成的实例,还是B直接生成的实例呢?还是C直接生成的实例呢?

分析:这就要用到原型的constructor属性了。

  • foo.__proto__.constructor === Foo的结果为true,但是 foo.__proto__.constructor === Object的结果为false。

所以,用 consturctor判断就比用 instanceof判断,更为严谨。

new 运算符

当new Foo()时发生了什么:

(1)创建一个新的空对象实例

(2)将此空对象的隐式原型指向其构造函数的显示原型。

(3)执行构造函数(传入相应的参数,如果没有参数就不用传),同时 this 指向这个新实例。

(4)如果返回值是一个新对象,那么直接返回该对象;如果无返回值或者返回一个非对象值,那么就将步骤(1)创建的对象返回。

3.继承的几种方式

继承的本质就是原型链。继承的方式有几种?每种形式的优缺点是

方式1:借助构造函数

    function Parent() {
        this.name = 'Parent 的属性';
    } 
	function Child1() {
        Parent1.call(this);         //【重要】此处用 call 或 apply 都行:改变 this 的指向
        this.type = 'child1 的属性';
    }
    console.log(new Child1);

【重要】Parent1.call(this) 意思是:让Parent的构造函数在child的构造函数中执行改变this的指向,parent的实例 --> 改为指向child的实例。导致 parent的实例的属性挂在到了child的实例上,这就实现了继承。

结果表明:child先有了 parent 实例的属性(继承得以实现),再有了child 实例的属性。

分析:这种方式,虽然改变了 this 的指向,但是,Child1 无法继承 Parent1 的原型。也就是说,如果我给 Parent1 的原型增加一个方法:这个方法是无法被 Child1 继承的。

    Parent1.prototype.say = function () { };

方法2:通过原型链实现继承

    /* 通过原型链实现继承*/
    function Parent2() {
        this.name = 'Parent 的属性';
    }
    function Child2() {
        this.type = 'Child 的属性';
    }
    Child2.prototype = new Parent2(); //【重要】
    console.log(new Child2());

打印结果:

【重要】Parent的实例赋值给了Childprototye,从而实现继承。此时,Child构造函数、Parent的实例、Child的实例构成一个三角关系:

  • new Child.__proto__ === new Parent()的结果为true

**分析:**这种继承方式,Child 可以继承 Parent 的原型

缺点:如果修改 child1实例的name属性,child2实例中的name属性也会跟着改变

造成这种缺点的原因是:child1和child2共用原型。即:chi1d1.__proto__ === child2__proto__是严格相同。而 arr方法是在 Parent 的实例上(即 Child实例的原型)的。

方式3:组合的方式:构造函数 + 原型链

就是把上面的两种方式组合起来:

    /*组合方式实现继承:构造函数、原型链 */
    function Parent3() {
        this.name = 'Parent 的属性';
        this.arr = [1, 2, 3];
    }
    function Child3() {
        Parent3.call(this); //【重要1】执行 parent方法,实现继承的方式Father.call(this)
        this.type = 'Child 的属性';//parent3的实例属性挂载到child上
    }
    Child3.prototype = new Parent3(); //【重要2】第二次执行parent方法
    var child = new Child3();

这种方式,能解决之前两种方式的问题:既可以继承父类原型的内容,也不会造成原型里属性的修改。

这种方式的缺点是:让父亲Parent的构造方法执行了两次。

方式4:prototype赋等

Child4.prototype=Parent4.prototype;

方式5:Object.create

Child5.prototype=Object.create(Parent5.prototype);
Child5.prototype.constructor=Child5

Object.create创建对象的方法,是用原型链对接

4.this指向问题

this指向

解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this,this指向的是一个对象,这个对象我们称为函数执行的 上下文对象。

根据函数的调用方式的不同,this会指向不同的对象:【重要】

  • 1.以函数的形式调用时,this永远都是window。比如fun();相当于window.fun();
  • 2.以方法的形式调用时,this是调用方法的那个对象
  • 3.以构造函数的形式调用时,this是新创建的那个对象
  • 4.使用call()和apply()调用时,this是指定的那个对象

箭头函数中this的指向

ES6中的箭头函数并不会使用上面四条标准的绑定规则,而是会继承外层函数调用的this绑定(无论this绑定到什么)。

call()和apply()

这两个方法都是函数对象的方法,需要通过函数对象来调用。

当函数调用call()和apply()时,函数都会立即执行

  • 改变this的指向
  • 实现继承。Father.call(this)
  • 显示绑定:第一个参数都是this要指向的对象(函数执行时,this将指向这个对象),后续参数用来传实参。

第一个参数的传递

1、thisObj不传或者为null、undefined时,函数中的this会指向window对象(非严格模式)。

2、传递一个别的函数名时,函数中的this将指向这个函数的引用

3、传递的值为数字、布尔值、字符串时,this会指向这些基本类型的包装对象Number、Boolean、String。

4、传递一个对象时,函数中的this则指向传递的这个对象。

call()和apply()的区别

call()和apply()方法都可以将实参在对象之后依次传递,但是apply()方法需要将实参封装到一个数组中统一传递(即使只有实参只有一个,也要放到数组中)。

如果是通过call的参数进行传参,是这样的:

	persion1.say.call(persion2, "实验小学", "六年级");

如果是通过apply的参数进行传参,是这样的:

	persion1.say.apply(persion2, ["实验小学", "六年级"]);
bind()
  • 都能改变this的指向
  • call()/apply()是立即调用函数
  • bind()是将函数返回,因此后面还需要加()才能调用。

bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。而原函数 func 中的 this 并没有被改变,依旧指向全局对象 window。

使用方法:

function func(a, b, c) {
    console.log(a, b, c);
}
var func1 = func.bind(null,'zoexyf');

func('A', 'B', 'C');            // A B C
func1('A', 'B', 'C');           // zoexyf A B
func1('B', 'C');                // zoexyf B C
func.call(null, 'zoexyf');      // zoexyf undefined undefined

在低版本浏览器没有 bind 方法,我们也可以自己实现一个。

手写bind方法实现(重要)

if (!Function.prototype.bind) {
        Function.prototype.bind = function () {
            var self = this,                        // 保存原函数
                context = [].shift.call(arguments), // 保存需要绑定的this上下文
                args = [].slice.call(arguments);    // 剩余的参数转为数组
            return function () {                    // 返回一个新函数
                self.apply(context,[].concat.call(args, [].slice.call(arguments)));
            }
        }
    }

5.闭包

闭包理解

闭包概念

闭包就是能够读取其他函数内部数据(变量/函数)的函数。

只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量或函数时, 就产生了闭包。

  • 闭包是嵌套的内部函数 ,包含被引用外部变量 or 函数的对象
产生闭包的条件

1.函数嵌套

2.内部函数引用了外部函数的数据(变量/函数)。

一种情况:外部函数被调用,内部函数被声明。比如:

  • 内部函数被提前声明,就会产生闭包(不用调用内部函数)
  • 采用的是“函数表达式”创建的函数,此时内部函数的声明并没有提前

常见的闭包

    1. 将一个函数作为另一个函数的返回值
    1. 将函数作为实参传递给另一个函数调用。
闭包1:函数作为另一函数的返回值
    function fn1() {
      var a = 2
      function fn2() {
        a++
        console.log(a)
      }
      return fn2
    }
    var f = fn1();   //执行外部函数fn1,返回的是内部函数fn2
    f() // 3       //执行fn2
    f() // 4       //再次执行fn2

当f()第二次执行的时候,a加1了,也就说明了:闭包里的数据没有消失,而是保存在了内存中。如果没有闭包,代码执行完倒数第三行后,变量a就消失了。

上面的代码中,虽然调用了内部函数两次,但是,闭包对象只创建了一个。

也就是说,要看闭包对象创建了一个,就看:外部函数执行了几次(与内部函数执行几次无关)。

闭包2. 函数作为实参传给另一函数调用
    function showDelay(msg, time) {
      setTimeout(function() { alert(msg) }, time)
    }
    showDelay('atguigu', 2000)

上面的代码中,闭包是里面的funciton,因为它是嵌套的子函数,而且引用了外部函数的变量msg。

闭包的作用

  • 作用1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
  • 作用2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

回答几个问题:

  • 问题1. 函数执行完后, 函数内部声明的局部变量是否还存在?

答案:一般是不存在, 存在于闭包中的变量才可能存在。

闭包能够一直存在的根本原因是f(实例),因为f接收了fn1(),这个是闭包,闭包里有a。注意,此时,fn2并不存在了,但是里面的对象(即闭包)依然存在,因为用f接收了。

  • 问题2. 在函数外部能直接访问函数内部的局部变量吗?

不能,但我们可以通过闭包让外部操作它。

闭包的生命周期
  1. 产生: 嵌套内部函数fn2被声明时就产生了(不是在调用)
  2. 死亡: 嵌套的内部函数成为垃圾对象时。(比如f = null,就可以让f成为垃圾对象。意思是,此时f不再引用闭包这个对象了)

闭包的应用:定义具有特定功能的js模块

  • 将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包含n个方法的对象或函数。
  • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能。
方式1:向外暴露含多个函数的对象

(1)myModule.js:(定义一个模块,向外暴露多个函数,供外界调用)

function myModule() {
    //私有数据
    var msg = 'Zoexyf Haha'
    //操作私有数据的函数
    function doSomething() {
        console.log('doSomething() ' + msg.toUpperCase()); //字符串大写
    }
    function doOtherthing() {
        console.log('doOtherthing() ' + msg.toLowerCase()) //字符串小写
    }
    //通过【对象字面量】的形式进行包裹,向外暴露多个函数
    return {
        doSomething1: doSomething,
        doOtherthing2: doOtherthing
    }
}

上方代码中,外界可以通过doSomething1和doOtherthing2来操作里面的数据,但不让外界看到。

(2)index.html:

<!--闭包的应用 : 定义JS模块
  * 具有特定功能的js文件
  * 将所有的数据和功能都封装在一个函数内部(私有的)
  * 【重要】只向外暴露一个包含n个方法的对象或函数
  * 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能-->
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
    var module = myModule();
    module.doSomething1();
    module.doOtherthing2();
</script>
方式2:匿名函数直接传给window对象

同样是实现方式一种的功能,这里我们采取另外一种方式。

(1)myModule2.js:(是一个立即执行的匿名函数)

(function () {  
   var msg = 'Zoexyf Haha' //私有数据
    //操作私有数据的函数
    function doSomething() {
        console.log('doSomething() ' + msg.toUpperCase())
    }
    function doOtherthing() {
        console.log('doOtherthing() ' + msg.toLowerCase())
    }
    //外部函数是即使运行的匿名函数,我们可以把两个方法直接传给window对象
    window.myModule = {
        doSomething1: doSomething,
        doOtherthing2: doOtherthing
    }
})()

(2)index.html:

<!--闭包的应用2 : 定义JS模块
  * 具有特定功能的js文件
  * 将所有的数据和功能都封装在一个函数内部(私有的)
  * 只向外暴露一个包信n个方法的对象或函数
  * 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能-->
<!--引入myModule文件-->
<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
    myModule.doSomething1()
    myModule.doOtherthing2()
</script>

上方两个文件中,我们在myModule2.js里直接把两个方法直接传递给window对象了。于是,在index.html中引入这个js文件后,会立即执行里面的匿名函数。在index.html中把myModule直接拿来用即可。

匿名函数更方便

闭包的缺点及解决

缺点:函数执行完后, 函数内的局部变量没有释放,占用内存时间会变长,容易造成内存泄露。

解决:能不用闭包就不用,及时释放。比如:

    f = null;  // 让内部函数成为垃圾对象 -->回收闭包

总而言之,你需要它,就是优点;你不需要它,就成了缺点。

内存溢出和内存泄露

内存溢出:一种程序运行出现的错误。当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误。

内存泄漏占用的内存没有及时释放。

注意,内存泄露的次数积累多了,就容易导致内存溢出。

常见的内存泄露

  • 1.意外的全局变量
  • 2.没有及时清理的计时器或回调函数—>clearInterval()
  • 3.闭包—>f = null //让内部函数成为垃圾对象–>回收闭包

个函数内部(私有的)

  • 只向外暴露一个包信n个方法的对象或函数
  • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能–>

上方两个文件中,我们在`myModule2.js`里直接把两个方法直接传递给window对象了。于是,在index.html中引入这个js文件后,会立即执行里面的匿名函数。在index.html中把myModule直接拿来用即可。

匿名函数更方便

### 闭包的缺点及解决

缺点:函数执行完后, 函数内的局部变量没有释放,占用内存时间会变长,容易造成内存泄露。

解决:能不用闭包就不用,及时释放。比如:

```javascript
    f = null;  // 让内部函数成为垃圾对象 -->回收闭包

总而言之,你需要它,就是优点;你不需要它,就成了缺点。

内存溢出和内存泄露

内存溢出:一种程序运行出现的错误。当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误。

内存泄漏占用的内存没有及时释放。

注意,内存泄露的次数积累多了,就容易导致内存溢出。

常见的内存泄露

  • 1.意外的全局变量
  • 2.没有及时清理的计时器或回调函数—>clearInterval()
  • 3.闭包—>f = null //让内部函数成为垃圾对象–>回收闭包

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值