最近这段时间一直在学习Javascript,对于习惯了java等面向对象编程的人来说,Javascript有不少反“直觉”的情况,有时候很难理解。所以决定边学习,边把一些碰到的“奇怪的”情况记录下来,一方面也方便以后自己注意,另一方面也顺便拿出来分享分享吧。
碰到了就会上来更新这篇文章。要说明的是,自己也是在边学习边总结,发现有意思的现象会立马记录上来,但不一定会马上写上回答。所以有看到的,且知道解答的,非常欢迎评论讨论,给出分析解答。感激不尽!
题1:
Window.name="";
function dog(name){
this.name=name;
}
var dog1=dog(“dog1”);
alert(this.name); //
Var dog2=new dog(“dog2”);
alert(this.name); //
两次alert的结果?
答案:第一次和第二次都是“dog1”。
原因:
首先明白两点:
1. 当在全局作用域中调用一个函数时,this对象总是指向Global对象(在浏览器中就是window对象)。
2. 使用new操作符调用构造函数实际上经历的4个步骤:
(1)创建一个新对象;
(2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
(3)执行构造函数中的代码(为这个新对象添加属性);
(4)返回新对象。
相信看完这两点就应该清楚了吧?尤其应该明白为什么第二个alert输出的还是dog1了吧?
题2:局部变量的值覆盖了全局变量的值?
var value="global";
function fun(){
var value="local";
alert(value);
alert(this.value);
}
如上代码在全局和函数内都定义了名为value变量。有些书或材料说局部变量覆盖了全局变量的值是不对的,有误导性。首先,实际上是“value“不是一个变量,而是两个变量,只是一个是全局的,一个是属于函数局部的。其次,函数内的value也并没有覆盖全局变量中的value。这两个value,至少在函数内都是存在的,取值且不一样,说明明显是两个不同的“对象”。恰当的说,应该是局部变量屏蔽了全局变量,也就是在函数中,如果没有定义value,则alert(value)中的value为“global”。定义了局部变量value后,从作用域链的理解就是,这个时候会首先取函数内的value了。发现有value,就不会在往上去取全局变量value。
题3:
for(var i=0;i<5;i++){
function s(){
alert("inner"+i);
}
s();
}
alert("outer");
弹出的顺序?inner0,inner1,inner2,inner3,inner4,outer
for(var i=0;i<5;i++){
function s(){
alert("inner"+i);
}
setTimeout(s,10);
}
alert("outer");
弹出顺序:outer,inner5, inner5, inner5, inner5, inner5
原因:
我的理解(注:凡是加了”我的理解“的,说明我对自己的原因分析没有完全把握):Javascript里面没有块级作用域,只有函数作用域,所以虽然有for循环,但执行环境一直是在全局环境下面。setTimeout后会停止当前的s函数,去执行执行环境下面的代码。
题4:
for(var i=0;i<5;i++){
var result=[];
result[i]=function s(){
alert("inner"+i);
} ;
}
执行代码后的result:
[undefined× 4, function s(){
alert("inner"+i);
}]。
也就是说只有result[4]赋值成功了。调用result[4]();会弹出打印“inner5”。
原因:(暂无)
for(var i=0;i<5;i++){
var result=[];
result[i]=function s(){
alert("inner"+i);
} ;
result[i]();
}
正常执行,打印:inner0,inner1,inner2,inner3,inner4.
原因:(暂无)
题5:
function Person(){};
var friend=new Person();
Person.prototype.name="aturbo";
alert(friend.name);//
Person.prototype={
constructor:Person,
name:"hasChange",
age:29
}
alert(friend.name);//
alert(friend.age); //
alert(Person.prototype.name);//
alert(Person.prototype.name);//
分别打印的是什么?
答案:从上到下:“aturbo”,”aturbo”, ””(undefined),”hasChange”,29。
原因:
Person.prototype.name="aturbo";表示给Person.prototype增加了一个属性。实例friend与原型(Person.prototype)连接的只不过是一个指针,因此实例可以在原型中找到新的name属性,并获取它的值。画图如下:
而第二种情况相当于重写了整个原型对象。我们知道,调用构造函数时(var friend=new Person())会为实例(friend)添加一个指向最初原型的[[prototype]]指针,而把原型修改为另一个对象就等于切断了构造函数与最初原型之间的联系。记住:实例中的指针仅指向原型,而不指向构造函数!!。画图如下:
题6:
function Person(){};
Person.prototype={
constructor:Person,
name:"aturbo",
age:29,
friends:["shel","dec"]};
var person1=new Person();
var person2=new Person();
person1.friends.push("van");
person1.friends; //["shel", "dec", "van"]
person2.friends; //["shel", "dec", "van"]
Person.prototype.friends;//["shel", "dec", "van"]
为什么修改person1的friends,person2的friends也跟着变了?
从Person.prototype.friends也跟着改变可以基本明白,person1和person2指向的应该是Person.prototype中的friends。friends数组仅仅存在于Person.prototype中。更改实例(person1)的引用类型值属性(friends),并没有像基本类型(如name属性)一样——更改person1后的name属性,等于在person1这个实例中新增了一个仅属于这个实例的name属性,所以不影响原型对象和其他实例。(具体可以参考《Javascript高级程序设计(第3版)》6.2.3(原型模式))。
通过构造函数模式不存在这种现象:
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["shelby","court"];
}
person1.friends.push("Van");
person1.friends;
["shelby", "court", "Van"]
person2.friends;
["shelby", "court"]
Person.prototype.friends;//undefined
通过Person.prototype//Person{}可以明白上面为什么是“undefined”。
也就是说,通过构造函数创建实例,构造函数Person里面的属性都是直接保存在实例中的,都是实例的属性,而不是继承Person.prototype来的属性:
person1.hasOwnProperty("name");//true
person1.hasOwnProperty("friends");//true
而:
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
}
person1.hasOwnProperty("sayName");//false
这个sayName属性是所有Person实例共享的。