node.js 之javascript 面向对象

本文探讨了javascript的面向对象特性,包括function、this、prototype等方面,并结合node.js的异步、事件驱动特点,深入浅出地解析了javascript中的对象创建、作用域和继承。同时,文章通过实例展示了如何利用Object.create实现继承,讨论了for...in循环和Object.defineProperties等细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

做完一段时间的嵌入式开发,又回到web开发了,陌生的感觉一点点。。。


之前看过不少node.js的热评,凑个热闹记住这个名字后后也没太在意,最近的项目中用到了一个新的java框架,其中种种魔术式的约定让人没有安全感,于是有了想深入探索的冲动。框架的资料说明不多,时间也不是很充裕,忍了一年多后还是跳进了源代码中。。。对框架刚有了点懵懵懂懂的感觉,于是又有了想玩转框架的冲动,世界框架万千种,如何将这个框架下的程序轻松地转化到其它框架?嘿嘿,思维发散似乎有点过了,想到了node.js(碗里面的刚吃一点,就想到锅里面的,主啊,求包养)

虽然不是太看好node.js,但好歹也喜欢javascript(javascript挂了一个java的羊头,node也完全可以自己搞一门程序语言,但由于有了性能良好的javascript引擎V8,以及javascript的简单语法,直接来了一个组合拼凑。这个发明人好像有点偷懒哦)。

看了很多说明,node.js最突出的特点是异步,事件驱动。事件驱动好说,哪个web画面上没有几个button、输入框,web开发整天跟这些打交道。至于异步接触最多的就是那个ajax了,点了一个按钮在程序中调用一个ajax的方法,不用等方法返回,然后该干嘛干嘛,ajax处理结束后会自动调用一个回调方法(ajax刚出道时很神秘,后来发现核心就是一个浏览器中的api时,对它的认识一直就凝固在此)。居然唠叨了这么半天,看来也太年轻++了。

写个微博主要是想让自己能深入一点了解,如果看官有异议,噢,求看官包养!

javascript的语法很简单,主要想关注一下其面向对象部分。

javascript的面向对象的实现方法跟现在主流不太一样,在javascript中提到继承,prototype这个关键字不可忽略(web开发中使用的javascript都很简单,都是写几个function之类的,这个关键字虽然很眼熟,也不能保证能完整地写出这个字母,而且还是在准备些这篇文章时才明白是一个有文字意义的单词:原型,雏形,蓝本),javascript中的对象是基于原型的。不明觉厉,好吧,从头开始吧。

-------------------------------------------------------------------

最开头是什么,对象嘛,非class莫属,很可惜,javascript 中class不是关键字,只是保留字,只能看不能用,这注定javascript中的对象跟java长得不一样了。

javascript也有内建的对象,最熟悉应该是Array了,

var arrs = new Array();

arrs[0] = 100;

.....

console.log(typeof Array);    ---function

console.log(typeof arrs);      ---object

原来,在javascript中,

对象是function,实例是object

关键字new貌似没有java中那么严谨:

var arrs = Array();

var arrs = new Array;

var arrs = new Array();

三条语句的效果是一模一样,狂野程序员的醉爱。

如果写成var arrs = Array;此时的arrs等于Array的别名。console.log(arrs == Array);    ---true

-------------------------------------------------------------------

最开头是什么,对象嘛,非function莫属

题外话:javascript中有一个内建的Function对象,就差第一个字母的大小写,其语法是

Function(参数1,参数2.。。,代码字符串)

var func = new Function("info", "console.log('in Function:' + info);");
func(111);

实际效果跟下面的程序一样:

function func(info) {console.log('in Function:' + info);}

func(111);

这个函数貌似没什么用处,跟下面的代码是等同的,

Function(arg1, arg2, codestr) = function(arg1, arg2){eval(codestr); return this;}


再来做个小小的测试:

var claz = function() {consoel.log("****");return 123;};
console.log(typeof claz);                 ---function
console.log(typeof claz());              ---****number
console.log(typeof (new claz()));   ---****object

console.log(claz);                             ---[Function]
console.log(claz());                          ---****123
console.log(new claz());                 ---****{}

怎么样,在new的作用下,function像不像class中的构造函数,构建了对象实例,不要new则是函数值返回值。

两种情况下,函数内部的程序都会执行。

把claz改成通常模式:

function claz() {
    console.log("****");
    return 123;
}

效果是一样的,只是两种不同的写法

-------------------------------------------------------------------

好吧,有了构造函数,那么对象的属性呢?

java中有this这个关键字,javascript中也有,在javascript中var声明了一个局部变量,this则表示实例

function claz(){
    var a = 456;
    this.a = 123;
    console.log("init:" + a);                             ---init:456      说明this的作用域比函数局部变量高
}

var cls = new claz();
console.log("property:" +cls.a);    ---property:123


this的作用域在构造器之外,看来是属于顶级杀器之类的了。如果有内部function的时候,这个this是啥情况呢?

function topfunc() {
    thiz = this;
    this.subfunc = function() {
        console.log(">>>" + (thiz == this));
    }
}
var cls = new topfunc();
new cls.subfunc();                 ---false
cls.subfunc();                         ---true

如此看来,当内部function只是作为简单的函数时,跟父function共享this;如果作为一个对象时,内部function拥有自己的this

那么this可以更改么,譬如:

function topfunc2() {
    this = new function(){}
}

很遗憾,编译都通不过。

其实var定义的变量也是一个实例,可以往实例中动态追加属性:

cls.a1 = "a1";
cls.show = function (){console.log("method call");}

console.log(cls.a1);      ---a1
cls.show();                      ---method call


如果这样

console.log(cls.this);    ---undefined


this和实例变量是不是一回事,再来测试一下:

function claz(){
    var thiz = this;
    this.check = function (clsv) {
        console.log(clsv == thiz);
    };
}
var cls = new claz();
cls.check(cls);                 ----true

也就是说,引用对象自己时,在function内部用this,在外部用变量名,有点意思。


如果this和变量名二合一不知道有什么效果

function func(){}

var fc = new func();
console.log(fc.this);     ----undefined

没这种用法,那么this.this呢:

function func(){
    this.this = this;
    this.p = 100;
}

var fc = new func();
console.log(fc.this);   --{ this: [Circular], p: 100 }

这种方法证明了猜测是可行的。不过这样也导致了循环引用,

console.log(fc.this.this.this.this.this.this.this.p);     ---100

这种用法是不是很酷?


既然可以动态往对象中追加属性以及方法,那么把一个对象的方法赋值给另外的对象,那么方法中变量的作用域会不会乱?

function topfunc() {
    this.a = 10;
    this.b = 20;
    this.subfunc = function() {
        console.log(">>>" + this.a);            ①
        this.b = 200 + this.a;         ②
        this.a = 1000;              ③

       console.log(">>>" + this.a);           ④
    }
}
function topfunc1() {
    this.a = 11;
    this.b = 21;
}
var cls = new topfunc();
var cls1 = new topfunc();
cls1.subfunc = cls.subfunc;
cls1.subfunc();                                       ---10  1000
console.log(cls.a + "," + cls.b);           ---10,20
console.log(cls1.a + "," + cls1.b);      ---1000,210

感觉有点乱,程序乱,log结果也乱,思绪也有点乱了:

按照变量作用域寻找路径,先找本地变量,然后父作用域,在①处,this.a还没有申明于是返回上一层,有点闭包的意思:

function topfunc() {
    var a = 100;
    var b = 200;
    return function subfunc() {
        var b = 210;
        a++;
        b++;
        console.log(a + "," + b);
    }
}

var topf = topfunc();
topf();                               ---101,211
topf();                               ---102,211


在subfunc中,a变量没有申明,于是其变量作用域找到上一层,一直引用着topfunc作用域中的变量,而b变量在subfunc已经申明过,其作用域是函数内部

所以上一个例子中的②就比较有意思了

this.b = 200 + this.a;

this.b算是在申明一个本地变量,此时的this是一个全局变量,于是this.b=cls1.b。而this.a在之前一直都是引用,还是cls.a,一旦对a进行赋值之类的,

 this.a = 1000;              ③

那么其性质也发生了变化,此时a的分身又割断了一条丝,被引用到cls1上,于是发生了上面怪怪的现象。

纷繁的表面现象下掩盖着许多真实的故事,真相是不是这样,不想再去看javascript引擎的源代码探个究竟,欢迎拍砖。

-------------------------------------------------------------------

谈了这么久,小可怜prototype该出来谢幕了

function topfunc() {}
var topf = new topfunc();

console.log(topfunc.prototype);             ---{}
console.log(String.prototype);                ---{}
console.log(topf.prototype);                    ---undefined

看来,这个prototype算是Class级,每个function对内建立prototype这么一个属性,而实例则没有。


先看看prototype和上面介绍的动态添加属性的差异:

function topfunc() {}

var a = new topfunc();
var b = new topfunc();
a.a = 100;
console.log(a.a);     ---100
console.log(b.a);     ---undefined

a,b是两个独立的实例,享有自己的空间,a添加了属性a,是不会影响其它实例的


如果是prototype的话:

function topfunc() {}
var a = new topfunc();
var b = new topfunc();
topfunc.prototype.a = 100;
console.log(a.a);     ---100
console.log(b.a);     ---100

看来prototype是Class级的,往其添加的属性、函数将会影响所有的实例。


稍微修改一下:

function topfunc() {}
var a = new topfunc();
var b = new topfunc();
a.a = 99;
topfunc.prototype.a = 100;
console.log(a.a);     ---99
console.log(b.a);     ---100

上例中,a变量中添加了a属性,然后在topfunc.prototype添加了a属性,log打印的结果表明a变量中添加的属性是优先的。

也就是说prototype的作用域比this还要高一级,哪里是什么小可怜,是大boss:


同时也可以看到,prototype可以在new 实例化之前,也可在其之后,代码执行时是看最近变化的值,感觉prototype对于实例来说就是一个全局变量。

function func(){}
var a = new func();
func.prototype.p = 100;
func.prototype = null;
console.log(a.p); ---100

把func的prototype设置成了null,还是能打印出prototype中添加的属性,说它是全局变量还是不妥,再来看下面的:

function func(){}
var c = {p : 200};
var a = new func();
func.prototype = c;
console.log(a.p);   ---undefined

在对象实例化后,将prototype设置为自定义对象,此时自定义对象中的属性找不到。如果将赋值语句提前到实例化之前,就可以使用属性了。


难道是在解析function的时候,创建了一个object,function实例化时将内部指针也指向这个object,也就是说function的实例以及function本身都引用这个对象?

那么将func.prototype = null;后,实例仍然可以使用的现象就好说了,func.prototype=null,但是实例还是引用初始prototype对象。

同样func.prototype.p = 100;放在实例化之前/之后都可以影响代码,func.prototype和实例引用同一个对象,因此有全局变量的感觉。

在最后一个例子中,function的prototype和实例引用的对象是不相同的,实例肯定不能引用function中prototype

function func(){}
func.prototype.p = 300;
var c = {p : 200};
var a = new func();
func.prototype = c;
console.log(a.p);     --300 //实例a还是指向初始化时的prototype
var a1 = new func();   //实例a1指向新的prototype,也就是c
console.log(a1.p);  ---200

例子证明了猜测还是正确的。


如果想通过实例而影响function的值,

function func(){}
func.prototype.p = 300;
var a = new func();
a.p = 400;
console.log(a.p, func.prototype.p);   ---400 300

看来是不可行的,实际上在a.p = 400;时,实例就创建了一个属性。


在javascript最遗憾的事莫过于字符串没有trim函数,往往都是调用一个自定义函数来实现,多么希望“dont't trim me”.trim(),prototype可以满足这个癖好:

String.prototype.trim = function(raw) {return "trimed";};

console.log("dont't trim me".trim());      ----trimed

怎么样,威力大吧。

当然还可以对已有的函数进行覆盖:

String.prototype.substr = function(s1, s2) {return "substring";};
console.log("dont't trim me".substr(1,2));      ----substring


再来看看prototype是什么:

console.log(typeof String.prototype);   ---object

噢,一个实例,那么也可以这样咯:

String.prototype = null;

console.log("dont't trim me".substr(1,2));      ----TypeError: Object #<Console> has no method 'substr'


那么function,prototype,实例之间都有些什么瓜葛呢,看看下面的例子(今天头有点晕,例子难免受影响)

function topfunc() {
    this.show = function subfunc() {console.log("sub function");}
    //show1在函数变量域中不存在,往上寻找也不存在,报[ReferenceError: show1 is not defined]错误

    try{show1();}catch(e){console.log(e);} 

    //topfunc.prototype.show1,直接指明是function对象,运行正常
    topfunc.prototype.show1();
}

topfunc.prototype.show1 = function(){console.log("show1");}

//在实例的范围中搜索show1,看来function的prototype也在搜索的范围之内
new topfunc().show1();
topfunc.prototype.show1();

//show1只是function属性prototype的属性,报[has no method 'show1']错误
try{topfunc.show1();}catch(e){console.log(e);}

//show是topfunc实例的一个属性,不是function级的,报[TypeError: Object #<topfunc> has no method 'show']错误

try{topfunc.prototype.show();}catch(e){console.log(e);}

//下面的用法报[has no method 'show1']错误
try{topfunc.show();}catch(e){console.log(e);}

从上面的例子可以看出,function和实例之间的属性是分开的,互不干涉。只是在实例中调用方法时,会搜索function的prototype。


不相信的话可以将上面的

try{show1();}catch(e){console.log(e);}

改为:

try{this.show1();}catch(e){console.log(e);}

新的语句运行良好。


如果将prototype换成别的自定义属性,结果会是什么样:

topfunc.prototype = new function(){};
topfunc.prototype.show1 = function(){console.log("show1");}

运行良好,看来prototype也不过如此,一普通对象。

利用这个特性,可以简单地实现继承

var a = {show : function() {console.log("inherit.....");}};
function func(){}
func.prototype = a;

new func().show();


再试试有什么需要注意的:

function topfunc() {
    try{this.show1();}catch(e){console.log(e);}
}

topfunc.prototype = new function(){};
topfunc.prototype.show1 = function(){console.log("show1");}

topfunc();                     // TypeError: Object #<Object> has no method 'show1']
new topfunc();

由上看出,prototype中的对象绑定是在对象实例化的时候进行的。


String.prototype.trim = function(){return ""};
var c = "aa".trim();

上面的比较特殊,"aa"本身就包含一个实例化的过程。


function show0(){console.log("show0");}
function topfunc() {
    this.show0();                  //show00
    show0();                         //show0
}
topfunc.prototype.show0 = function(){console.log("show00");};
new topfunc();

如果不特别指明是实例的方法,则默认为是function层次,代码不会去搜索实例的prototype


function topfunc() {
    try{this.show1();}catch(e){console.log(e);}
}

topfunc.myprototype = new function(){};
topfunc.myprototype.show1 = function(){console.log("show1");}

new topfunc();

由上看出,prototype还是有点特殊的,换其它的还不行。

除了这个prototype之外,function还有那些属性呢:

function topfunc(){this.show1=function(){};}

var props = Object.getOwnPropertyNames(topfunc);
for(var p in props){
    console.log(props[p]);
}

打印出如下信息:

length
name
arguments
caller
prototype

如果打印实例的属性:

var props = Object.getOwnPropertyNames(new topfunc());

则显示:

show1

-------------------------------------------------------------------

还记不记得开头的console.log(new claz());                 ---****{}

实例的符号表示是{},我们都知道{}一般用来封装代码段,难道还能表示实例

function topfunc() {
    this.fun1 = function(){};
    this.fun2 = function(){};
    this.p1 = 100;
}
console.log(new topfunc());

打印结果:{ fun1: [Function], fun2: [Function], p1: 100 }

嗯,代码是分号分隔,实例是逗号分隔,那么上面的代码可以等换为:

var fun1 = {
  fun1: function(){},
  fun2: function(){},
  p1 : 100
};
console.log(fun1);

打印结果:{ fun1: [Function], fun2: [Function], p1: 100 }

用这种方法可以直接实例化一个对象,形式是key:value,key的前面不需要this,而且不能要

var fun1 = {
  this.fun1: function(){},    //属性不能有this
  var a = 100,                      //属性中不能有代码
  blank: var b = 200
};

如果还有点印象的话,一定还会想起[]

console.log(new Array());    // []

这个[]其实也是一个对象,表示一个对象数组

var as = [1,2,3,4];
console.log(as instanceof Array);       //true

-------------------------------------------------------------------

呼,终于把一些问题点给理顺了

面向对象可不能没有继承,但是extend也不能用,既然不愿实现,何必苦苦哀求。再说了javascript又不是java

不过来抖一抖相关的趣事秘闻,先从Object对象来谈谈:

var obj1 = {
fun1 : function(){},
p1 : 100
};

var obj2 = Object.create(obj1);
obj2.p2 = 200;
for(var p in obj2){console.log(p);}

===

p2
fun1
p1

obj2经过Object.create,把obj1的属性全数继承了过来,像不像继承?

×题外话,这个for in倒是挺猛的,可以把实例的属性全部枚举出来,试试打印其它对象的:

var obj2 = "";
for(var p in obj2){console.log(p);}

打印为空,奇怪了,字符串有很多方法的,怎么可能为空。不看源代码单纯猜测还真是没安全感。

不过Object的另外一个函数让人怀疑javascript内部还有蹊跷:

var obj1 = {fun1 : function(){},p1 : 100};

Object.defineProperties(obj1,{
    p1 : {enumerable:false, value:200}
});

var obj2 = Object.create(obj1);
obj2.p2 = 200;

for(var p in obj2){console.log(p);}

===

p2
fun1

在Object.defineProperties的函数中,将p1的enumerable设置成了false,for in中就不会打印了,原来for in会调用一个变量的enumerate接口,同样Object.keys(obj1)也会调用。

如果在程序末尾调用obj2.p1;或者obj1.p1,程序是没有问题的,这个设置正如其字面上的意思。

这个defineProperties中除了enumerable外,还有

writable,value
set,get
这两组是互斥的,后面的设置function,前面的设置值。还有一个configurable,用以设置可不可删除对象的属性:delete obj.prop

然后apply和call





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值