本文深入解析JavaScript中的继承机制与闭包概念。详细阐述扩展Object原型、利用call/apply以及原型链指向三种继承实现方式,并探讨闭包在访问私有变量和保持变量内存中的作用。通过经典案例演示如何利用闭包解决循环事件绑定中的变量共享问题,帮助开发者掌握JS面向对象编程的核心技巧。
前言
在前两篇文章中,我们深入探讨了JavaScript面向对象编程(OOP)的核心知识点,包括、以及原型链等相关内容。今天,我们将继续深入,探讨面向对象编程中剩余的关键部分。
众所周知,面向对象编程具备三大特征:封装、继承和多态。在JavaScript中,封装主要通过私有属性和私有方法来实现。然而,JavaScript并不支持多态,因此严格来说,JavaScript是一门基于对象的语言,而非纯粹的面向对象语言。在这三大特征中,继承在JavaScript中显得尤为重要。
继承的基本概念与实现方式
一、继承的基本概念
继承允许一个子类自动拥有父类的属性和方法,从而复用代码。
继承涉及两个类之间的关系:子类与父类。
因此,继承的本质就是让子类获取父类的所有属性和方法。在JavaScript中,我们通常使用三种常用方法来模拟实现这一过程。
这三种方法分别是:扩展Object的prototype属性实现继承、使用call和apply方法实现继承,以及使用原型链实现继承。
二、扩展Object的prototype实现继承
这种继承方式的本质是自定义一个方法,通过遍历循环,将父类的所有属性和方法逐个复制给子类。
具体操作步骤如下:
第一步:定义父类构造函数。
functionParent(){}
第二步:定义子类构造函数。
funtion Son(){}
第三步:通过修改Object的原型,添加一个扩展方法。
Object.prototype.customExtend =function(parObj){
for(variinparObj){//通过for-in循环,把父类的所有属性方法,赋值给自己
this[i] =parObj[i];
}
}
第四步:子类对象调用该扩展方法,从而获得父类的属性和方法。
Son.customExtend(Parent);
三、使用call和apply实现继承
首先,为了使用这种方式实现显式继承,我们需要回顾一下call和apply函数的作用:
call和apply的作用是通过函数名调用方法,并强行将函数内部的this指向指定的对象。
call的语法为:func.call(func的this指向的obj, 参数1, 参数2...);
apply的语法为:func.apply(func的this指向的obj, [参数1, 参数2...]);
使用这两个函数实现继承的思路是:在子类中调用父类函数,并使用call或apply将父类的this强行绑定为子类的this。这样,父类中绑定在this上的属性和方法,就会自然地绑定到子类的this上。
具体操作步骤如下:
第一步:定义父类构造函数。
funtion Parent(){}
第二步:定义子类构造函数。
functionSon(){}
第三步:在子类构造函数内部,通过call或apply方法调用父类构造函数。
functionSon(){
Parent.call(this,....);//将父类函数中的this,强行绑定为子类的this}
四、使用原型实现继承
使用原型实现继承是一种简单且易于理解的方式,其核心是将子类的prototype指向父类的实例对象。
具体操作步骤如下:
第一步:定义父类构造函数。
functionParent(){}
第二步:定义子类构造函数。
functionSon(){}
第三步:将子类的原型对象声明为父类的实例,即Son.prototype = new Parent();。
Son.prototype =newParent();
JavaScript作用域与闭包原理
五、闭包
要深入理解闭包,首先需要了解JavaScript中的作用域机制:
1、JavaScript中的作用域
全局变量:在函数外部声明的变量。
局部变量:在函数内部声明的变量。
在JavaScript中,函数是唯一的局部作用域,而if、for等其他代码块{}并不拥有自己的作用域。
因此,函数外部无法访问局部变量。实际上,当函数执行完毕后,其占用的内存通常会被释放。
2、闭包
在前文概述中提到的面向对象三大特征之一“封装”,可以通过函数的私有属性来实现,这里的私有属性实际上就是局部变量。
然而,封装的目的是限制外部访问,而非完全拒绝。那么,如何才能在函数外部访问这些私有的局部变量呢?答案就是闭包。
JavaScript提供了一种“闭包”的概念:在函数内部定义一个子函数,利用子函数访问父函数的私有变量。执行完相关操作后,将子函数通过return返回。
代码示例如下:
functionfunc2(){varnum = 1;functionfunc3(){varsum = num+10;
alert(sum);
}returnfunc3;
}varf =func2();
f();
3、闭包的作用:
① 访问函数的私有变量;
② 使函数的变量始终保留在内存中,而不被垃圾回收机制释放。
4、闭包的典型应用
我们来看一个常见功能:页面中有6个li元素,要求点击每个li时,弹出该li对应的序号。
HTML代码相对简单:
那么,JavaScript代码应该如何编写呢?许多初学者可能会这样写:
varlis = document.getElementsByTagName("li");for(vari=0;i
lis[i].onclick=function(){
alert("您/点击了第"+i+"个li!");
}
这种写法正确吗?答案是否定的!我们来分析一下:页面加载时,JavaScript代码会立即执行,即上述for循环在页面加载完成后就已经执行完毕。此时,变量i的值已经变为lis.length。因此,当你点击任意li时,无论点击哪一个,弹出的都是lis.length的值。
那么,我们应该如何修改代码呢?请看下面的代码:
varlis = document.getElementsByTagName("li");for(vari=0;i
lis[j].onclick=function(){
alert("您/点击了第"+j+"个li!");
}
}();
}
区别在哪里?仔细观察可以发现,我们在for循环外部嵌套了一层自执行函数。这种函数套函数的形式,就形成了闭包。
其作用是什么?正如前文所述,闭包中的自执行函数拥有独立的作用域。在内部代码执行期间,自执行函数中的变量j不会被释放。
也就是说,循环执行了6次,生成了6个独立的函数空间。每个空间中都有自己独立的j变量,因此最终不会出现所有li点击都显示相同序号的问题。
总结
以上所述是小编给大家介绍的JS继承与闭包及JS实现继承的三种方式,希望对大家有所帮助!