话不多说,直接上题,代码如下:
function Foo (){
getName = function (){
alert(1);
};
return this;
}
Foo.getName = function (){
alert(2);
};
Foo.prototype.getName = function () {
alert(3);
};
var getName = function (){
alert(4);
};
function getName (){
alert(5);
};
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
看看这题的各部分输出是多少?
只要能把这道题完全做对,基本上在笔试题上就能拿下不低的分了。当然了,这题不仅仅考的是面向对象程序设计,它还考察了其他的基础知识点,而这些知识点也许刚刚好是大家遗漏的,不信?大家可以先试试能做对多少?
下面来看对于这道题需要的知识点:
1)首先大家要明白公有属性、公有方法、私有属性、私有方法、静态属性和静态方法的区别与使用
2)然后要明白一些运算符的运算顺序和js程序执行的顺序
3)最后要明白和理解原型链、继承和this的指向问题
知识点1:那么这里先举一个例子说明公有属性、公有方法、私有属性、私有方法、静态属性和静态方法的区别,代码如下:
function User(name){
var name = name;//私有属性
this.name = name;//公有属性
function getName (){//私有方法
return name;
};
this.getName = function (){//公有方法
return this.name;
}
}
User.prototype.getName = function (){//公有方法
return this.name;
}
User.name = 'Charles';//静态属性
User.getName = function (){//静态方法
return this.name;
}
var userCharles = new User('Charles');//实例化
那它们的使用是怎么样的呢?
1.调用公有方法,公有属性必须先实例化对象,即用new操作符实例化对象;
2.公有方法不能调用私有方法和静态方法;
3.静态方法和静态属性无需实例化即可调用;
4.对象的私有方法和属性,外部是不可以访问的。
知识点2:再来看一些运算符的运算顺序和js程序执行的顺序
js执行顺序是按照编写的顺序执行的,但是一定要知道js预解析的时候,变量声明和函数声明是要提前的,且变量声明更优先(可参考链接:js变量名与函数名重名的问题)。另外js异步回调也是其中之一的知识点,大家可以参考链接:关于js异步回调
知识点3:要明白和理解原型链、继承(可参考链接:js原型、原型链与继承的理解)和this的指向问题(可参考链接:js中this的作用和使用方法)
好,复习完上述三个知识点之后,大家再做一做这道题,看能做对几问呢?
该题的上半部分定义了一个叫Foo的函数对象,之后为Foo创建了一个名为getName的静态方法,随后给Foo的原型对象创建了一个名为getName的公有方法,然后又通过函数表达式创建了一个getName的匿名函数,最后声明一个名为getName的函数。
下面来看第一题:Foo.getName();
由于是直接调用Foo的getName方法,但由于此时并没有实例对象,故这里是访问不到Foo函数对象里的getName方法的,而且Foo函数对象里的getName方法是私有方法,根本访问不到。
进一步分析,我们明白js在预解析的时候会将函数声明置顶,所以执行时代码应如下:
//函数声明置顶
function getName (){
alert(5);
};
function Foo (){
getName = function (){
alert(1);
};
return this;
}
Foo.getName = function (){
alert(2);
};
Foo.prototype.getName = function () {
alert(3);
};
var getName = function (){
alert(4);
};
好,函数声明置顶之后,跟Foo调用getName方法没关系,所以代码执行下来,Foo函数访问的自然是存在其自身上的静态方法getName方法,所以该题答案是2。有的同学可能会问,为什么不是4?其实可以把最下面的函数表达式分割成这样:
var getName;
getName = function (){
alert(4);
};
就是js在解析函数表示式时,先将变量getName声明置顶,然后将一个匿名函数赋值给getName。而变量的调用就不用我多说了吧,跟方法的调用形式是不一样的。
第二题:getName();
这里很明显的是直接调用getName函数,按道理来说,是可以直接输出5的,但是这题并不是,大家发现没,这里出现了变量名和函数名重名的问题了!这里我再讲解一次吧,下面来看函数声明和函数表达式的区别:
//函数声明
function funName(){
alert(123);
}
//函数表达式
var funName = function (){
alert(123);
}
理解区别之后,再来额外看一道问题:在一个程序里同时用函数声明和函数表达式定义一个名为getName的函数
getName();//woman
var getName = function (){
console.log('man');
}
getName();//man
function getName (){
console.log('woman');
}
getName();//man
这里涉及js预解析的知识之函数声明和变量声明提升,变量声明提升更优先,所以上述代码等于:
function getName (){//函数声明提升
console.log('woman');
}
var getName;//变量声明提升,此时为undefined
getName();//woman,还未执行到函数表达式且还未赋值,所以执行的是函数声明的值
getName = function (){//这里因为赋值,覆盖了函数声明的定义
console.log('man');
}
getName();//man,这里就执行了函数表达式的值
getName();//man,同上
这里说明下,也算是总结:js中函数声明和函数表达式是存在区别的,函数声明在js解析时会进行函数提升,因此在同一个作用域内,不管函数声明在哪定义,该函数都可以调用。而函数表达式的值是在js运行时确定,且要等到表达式赋值完成后该函数才能调用,跟变量调用类似。
所以第二问的答案为4,执行的是函数表达式里的值。
第三题:Foo().getName();
解这题的关键在于理解this的指向问题。首先计算Foo函数,而Foo返回的是this(很明显指向的是window,因为是直接调用),然后计算this.getName()(相当于window.getName()),而我们知道Foo函数内部有一个函数赋值语句,且名为getName,所以按照作用域来讲,肯定是先查找Foo内部作用域里是否有声明getName这个变量或者函数,所以查找结果是没有的,因为没有var声明;随后向外(函数外)查找,发现有一个var getName = function (){alert(4);}声明了getName变量,并且赋值了,但是在进行计算getName()时,是按照作用域的顺序计算的(也就是按照代码的执行顺序来的),即先查找Foo函数内部有没有对变量getName进行赋值,有则优先计算,否则再查找外部是否有对getName进行赋值然后再计算。在代码执行时发现在Foo函数内部有一个对getName变量赋值的语句,相当于把外部的赋值语句给覆盖了,于是结果输出1。(如果在外部没查找到,就会在全局环境下创建一个名为getName的变量)
第四题:getName();
直接调用getName(),相当于执行window.getName(),因为这个getName变量在执行Foo().getName();的时候被改变了,输出为1,因此结果应该与第三题相同,输出也为1.
第五题:new Foo.getName();
这题需要知道运算符号的优先顺序,点的优先级比new无参数列表优先级高,当点运算完后又因为有个()就变成new有参数列表,所以直接运行new。
new(有参数列表)>点运算符>函数调用>new(无参数列表)
故该题的执行顺序应该是这样子:new (Foo.getName)();
这里实际上是将Foo.getName作为构造函数来执行的,因此输出2.
第六题:new Foo().getName();
跟第五题类似,执行顺序(若是同级,则按从左到右的顺序执行)应该是这样的:new有参数列表>点运算符>函数调用>new无参数列表,即(new Foo()).getName();
这里还涉及到了一个小的知识点:构造函数的返回值
在传统语言中,构造函数不应该有返回值,实际执行的返回值就是此构造函数的实例化对象,而在js中构造函数可以有返回值也可以没有。
1)若没有则返回实例化对象
2)若有则检查返回值是否为引用类型。如果是非引用类型,如String,Number,Boolean,Null,Undefined则与无返回值相同;如果是引用类型,则实际返回值为这个引用类型
原题中,由于返回的是this,而this在构造函数中本来就代表当前实例化对象,最终Foo函数返回的就是实例化对象。之后调用的是实例化对象的getName方法,学过原型链的孩子应该知道,在查找方法的时候是按照原型链的方式查找的,首先要查找原型链的开始端,也就是Foo函数内部是否有公有方法getName,发现没有,往原型链上面查找,在Foo函数的原型对象(Foo.prototype)上查找到了getName方法,于是就输出3。
第七题:new new Foo().getName();
同理首先要判断其执行顺序,最终执行顺序为:new ((new Foo()).getName)();即先舒适化Foo的实例化对象,然后将其原型上的getName方法作为构造函数再次new(实例化),然后计算,结果为3
好的,这个答题的分析就结束了,总的来说,做完这道题可以将我们很多的重要知识点重新拾起,同时也帮我们进一步巩固了这些js常考的知识点,与君共勉。