本文深入解析JavaScript面向对象编程核心概念。从Object基类的使用出发,详解静态属性与方法的模拟实现。重点剖析闭包的作用域链机制及其在保持变量内存中的应用,阐述私有属性的构造模式。最后对比call与apply的区别,并介绍扩展Object、构造函数借用及原型继承三种实现方式。
Object类与静态属性
1.Object类
在JavaScript中,Object是所有自定义类的基类。利用Object类创建对象时,无需显式定义构造函数,即可直接添加属性。
var per = new Object();
per.name = 'zhangsan';
per.age = ;
alert(per.name + per.age);
当程序需要一个能存储大量数据的对象变量时,直接使用Object类是一个简便选择,因为它避免了对构造函数的定义。此外,Object类提供的hasOwnProperty方法常用于检查对象自身是否包含特定属性。
var per = new Object();
per.name = 'zhangsan';
per.age = ;
if per.hasOwnProperty('email'){
alert('具有email');
}else{
alert('无email');
}
2.静态属性
在部分面向对象语言中,static关键字用于定义类的静态属性或方法。JavaScript虽无原生static关键字,但可通过特定语法模拟这一功能。
语法:
类名.属性名
类名.属性=function(){}
function Person(){
}
Person.count = ;
var p = new Person();
Person.count++;
var p = new Person();
Person.count++;
var p = new Person();
Person.count++;
alert(Person.count);
通过上述语法,可以为类添加静态属性和静态方法。
function Person(){
Person.count++; //静态属性
Person.getCount=function(){ //静态方法
alert('当前共有' + Person.count + '个人');
}
}
Person.count = ;
var p = new Person();
var p = new Person();
var p = new Person();
Person.getCount();
闭包原理与内存管理
3.闭包
闭包是指一个包含变量及其绑定环境的表达式(通常是函数),这些变量成为该表达式的一部分。
提出一个问题:
function display(){
var i=;
}
display();
//在这里,想访问局部变量i
由于作用域不同,全局代码无法直接访问局部变量i。此外,当display函数执行完毕后,局部变量i通常会被垃圾回收机制释放。闭包的核心功能在于“访问局部变量”以及“防止变量所占内存被释放”。
//例
function fn(){
function fn(){
alert('hello');
}
return fn; //返回fn函数首地址
}
var test=fn(); //test也指向了fn函数的首地址
test();
通过例1可知:变量可以指向函数的首地址,函数也可以返回另一个函数的首地址。
//例
function fn(){
var i = ;
function fn(){
alert(i);
}
return fn; //返回fn函数首地址
}
var test=fn(); //test也指向了fn函数的首地址
test();
通过例2可知:若使用一个外部函数包含变量i,则局部变量i的内存将不会被回收。
//例
function fn(){
var i = ;
function fn(){
alert(i++);
}
return fn; //返回fn函数首地址
}
var test=fn(); //test也指向了fn函数的首地址
test();
test();
test();
在例3中,由于i的内存永远不会被回收,每次调用fn2时,i的值会自增1。运行结果依次弹出10、11、12。
闭包原理分析:在例3中存在三个作用域:全局作用域、fn1作用域和fn2作用域。全局作用域中执行test=fn1(),这相当于让test指向fn2。在fn1作用域内定义了var i=10并返回fn2;在fn2作用域内执行alert(i++)。当全局执行test=fn1()时,test指向了fn2的作用域,此时fn2作用域下的i被全局作用域“钩住”。根据作用域链规则,fn2内部未定义i,因此向上层查找,在fn1作用域中找到var i=10。全局的test钩住了fn2的i,fn2的i又钩住了fn1的i,因此即使fn1运行完毕,其局部变量也不会被回收。
私有属性与函数调用技巧
4.私有属性
在面向对象思想中,敏感或不希望公开的成员可定义为私有。JavaScript可通过特定语法模拟此功能。
语法:
function Person(p_name){
var name = p_name;
this.age
}
var:表示私有成员
this:表示公有成员
function Person(p_name,p_age){
this.name = p_name;
var age = p_age;
}
var p = new Person('zhangsan',);
alert(p.name);
alert(p.age);
在示例中,若尝试用var声明私有成员属性,Person构造函数执行完毕后,age变量会被回收,无法直接作为成员属性使用。
function Person(p_name,p_age){
this.name = p_name;
var age = p_age;
this.setAge=function(a){
age = a;
}
this.getAge=function(){
return(age);
}
}
var p = new Person('zhangsan',);
p.setAge();
alert(p.getAge());
通过this.setAge和this.getAge两个方法引用局部变量age,使得age变量在函数执行后仍被引用,从而不被回收。
若仅存在set方法,表明该属性为只写属性。
若仅存在get方法,表明该属性为只读属性。
5.call和apply的使用
call和apply的功能是调用指定对象作为上下文的当前函数。两者功能完全相同,仅在语法形式上略有差异。
语法:
call([thisObj[,arg1[,arg2[,argN]]]])
第一个参数:指定函数执行时this的指向对象
后续参数:按顺序指定传递给函数的参数
apply([thisObj[,argArray]])
第一个参数:指定函数执行时this的指向对象
第二个参数:数组,表示传递给函数的参数集合
在JavaScript中,函数有多种调用形式:
Person(); //Person内的this指向window
var p=new Person(); //Person内的this指向p
per.Person(); //Person内的this指向per
function Person(p_name,p_age){
this.name = p_name;
this.age = p_age;
}
function speak(){
alert(this.name + this.age);
}
var p = new Person('zhangsan',);
//speak(); 这样调用this指向window
//p.speak(); p对象没有speak属性
使用call和apply进行调用
function Person(p_name,p_age){
this.name = p_name;
this.age = p_age;
}
function speak(){
alert(this.name + this.age);
}
var p = new Person('zhangsan',);
speak.call(p);
speak.apply(p);
call和apply在执行时完成两件事:1)将函数内部的this指向第一个参数指定的对象;2)调用该函数。
另外,还可以采用以下方式解决问题:
P1.say=speak;
P1.say();
这种解决方式与前述方法有本质区别:
前述方法直接调用speak函数,仅改变函数内部this的指向。
上述方法会为p1对象增加属性,导致p1对象的体积变大。
举例说明:
<script>
function fn(){
this.style.color='red';
}
function fn(){
this.style.fontSize='px';
}
window.onload=function(){
document.getElementById('btn').onclick=function(){
var div = document.getElementById('div');
fn.call(div);
fn.apply(div);
};
};
</script>
<div id='div'>hello javascript</div>
<input type='button' id='btn' value='确定'>
JavaScript继承的三种实现
6.继承的三种实现方法
概念:在部分面向对象语言中,子类可继承父类的属性和方法。此功能可在JavaScript中进行模拟实现。
三种方法:
第一种:扩展Object方法
Object.prototype.方法=function(父类对象){
for(var i in 父类对象){
this[i] = 父类对象[i];
}
};
举例说明:
Object.prototype.ext=function(parObject){
//循环遍历父类对象所有属性
for(var i in parObject){
//为子类对象添加这个遍历到的属性
//它的值是父类对象这个属性的属性值
this[i] = parObject[i];
}
}
function Person(p_name,p_age){
this.name=p_name;
this.age=p_age;
this.speak=function(){
alert(this.name+this.age);
}
}
function Student(p_no){
this.no=p_no;
this.say=function(){
alert(this.no+this.name_this.age);
}
}
var stu = new Student();
stu.ext(new Person('xiaoqiang',));
stu.speak();
stu.say();
第二种:使用call和apply方法
语法:
父类构造器.call(this,.......);
function Person(p_name,p_age){
this.name=p_name;
this.age=p_age;
this.speak=function(){
alert(this.name+this.age);
}
}
function Student(p_no,p_name,p_age){
this.no=p_no;
this.say=function(){
alert(this.name+this.age+this.no);
}
Person.call(this,p_name,p_age);
}
var stu = new Student(,'zhagsan',);
stu.speak();
stu.say();
第三种:原型继承
语法:
子类.prototype = new 父类();
function Person(p_name,p_age){
this.name=p_name;
this.age=p_age;
this.speak=function(){
alert(this.name+this.age);
}
}
function Student(p_no){
this.no=p_no;
this.say=function(){
alert(this.name+this.age+this.no);
}
}
Student.prototype = new Person('wangwu',);
var stu = new Student();
stu.speak();
stu.say();
以上内容介绍了JavaScript面向对象编程中Object类、静态属性、闭包、私有属性、call和apply的使用,以及继承的三种实现方法,希望对大家有所帮助!