JavaScript面试后的反思
来源:互联网 发布:mac不能玩魔兽世界 编辑:程序博客网 时间:2024/04/29 09:31
JavaScript面试后的反思
2010-08-01
写此文目的是为了让更多的程序员理解javascript的一些概念,对,是理解,而不是了解。我们已经了解得够多了,该是向深入理解的方向靠拢的时候了。
为什么这么说,前些日子收到面试邀请,那就去试试呗,有几年没有面试过了吧。和面试官坐在沙发上,聊天式的他问我答,以下就是几个javascript方面的问题:
请创建一个对象,包括几个公有属性,接下来是为对象创建一个公有方法,然后为对象创建几个私有属性,一个私有方法。
说实话,这几个问题我默名其妙,要是他让我用jquery写个拖动插件什么的,我估计我能写挺好,原生的javascript,晕,虽然我看过jquery源码解读,但这些基本概念要命。
本文的例子输出使用如下方法,便于查看:
1
function
dwn(s)
2
{
3
document.write(s+
"<br />"
);
4
}
function
从一开始接触到js就感觉好灵活,每个人的写法都不一样,比如一个function就有N种写法,如:
1
function
showMsg(){}
2
var
showMsg =
function
(){}
3
showMsg =
function
(){}
似乎没有什么区别,都是一样的嘛,真的是一样的吗,大家看看下面的例子:
01
///------------------------------------------------------------------------------------------------
02
//函数定义:命名函数(声明式),匿名函数(引用式)
03
//声明式,定义代码先于函数执行代码被解析
04
function
t1()
05
{
06
dwn(
"t1"
);
07
}
08
t1();
09
10
function
t1()
11
{
12
dwn(
"new t1"
);
13
}
14
t1();
15
16
//引用式,在函数运行中进行动态解析
17
var
t1 =
function
(){
18
dwn(
"new new t1"
);
19
}
20
t1();
21
22
var
t1 =
function
(){
23
dwn(
"new new new t1"
);
24
}
25
t1();
26
//以上输出:new t1,new t1,new new t1,new new new t1
可能想着应该是输出t1,new t1,new newt1,new new new t1,结果却并不是这样,应该理解这句话:声明式,定义代码先于函数执行代码被解析。
如果深入一步,应该说是scope链问题,实际上前面两个方法等价于window.t1,可以理解为t1是window的一个公有属性,被赋了两次值,以最后一次赋值为最终值。
而后面两个方法,可以理解为是t1是个变量,第四个方法的var去掉之后的结果仍然不会改变。
然而,当第四个方法改成function t1(){}这样的声明式时,结果变成了new new new t1,new new new t1,new new t1,new new t1前面两个按照我的理解可以很好的理解为什么是这个答案,第三个也可以理解,但是最后一个输出让我比较纠结。
另外匿名函数还有(function(){...})()这样的写法,最后一个括号用于参数输入。
还有var t1=new function(){..}这样的声明,实际上t1已经是一个对象了。
1
var
t2 =
new
function
()
2
{
3
var
temp = 100;
//私有成员
4
this
.temp = 200;
//公有成员,这两个概念会在第三点以后展开说明
5
return
temp +
this
.temp;
6
}
7
alert(
typeof
(t2));
//object
8
alert(t2.constructor());
//300
除此之外,还有使用系统内置函数对象来构建一个函数,例:
1
//这个位置加不加new结果都一样,WHY
2
var
t3 =
new
Function(
'var temp = 100; this.temp = 200; return temp + this.temp;'
);
3
alert(
typeof
(t3));
//function
4
alert(t3());
//300
创建对象
首先我们理解一下面向对象编程(Object-Oriented Programming,OOP),使用OOP技术,常常要使用许多代码模块,每个模块都提供特定的功能,每个模块都是孤立的,甚至与其它模块完全独立。这种模块化编程方法提供了非常大的多样性,大大增加了代码的重用机会。可以举例进一步说明这个问题,假定计算机上的一个高性能应用程序是一辆一流赛车。如果使用传统的编程技巧,这辆赛车就是一个单元。如果要改进该车,就必须替换整个单元,把它送回厂商,让汽车专家升级它,或者购买一个新车。如果使用OOP技术,就只需从厂商处购买新的引擎,自己按照说明替换它,而不必用钢锯切割车体。
不过大部分的论点是,javascript并不是直接的面向对象的语言,但是通过模拟可以做到很多面向对象语言才能做到的事,如继承,多态,封装,javascript都能干(没有做不到,只是想不到):
01
///-----------------------------------------------
02
//以下三种构造对象的方法
03
//new Object,实例化一个Object
04
var
a =
new
Object();
05
a.x=1, a.y=2;
06
//对象直接量
07
var
b = {x:1,y:2};
08
//定义类型
09
function
Point(x,y)
10
{
11
//类似于C#中的类
12
this
.x=x;
13
this
.y=y;
14
}
15
var
p =
new
Point(1,2);
//实例化类
第一种方法通过构造基本对象直接添加属性的方法来实现,第二种和第一种差不多,可以看成是第一种方法的快捷表示法。第三种方法中,可以以"类"为基础,创造多个类型相同的对象。
对象属性的封装(公有和私有)
以例子来说明:
01
function
List()
02
{
03
//私有成员,在对象外无法访问,如果此处无var声明,则m_elements将变成全局变量,这样外部是可以直接访问到的,如alert(m_elements[0])
04
var
m_elements=[];
05
m_elements=Array.apply(m_elements,arguments);
06
//此处模拟getter,使用时alist.length;
07
//等价于getName()方式:this.length=function(){return m_elements.length;},使用时alist.length();
08
//公有属性,可以通过"."运算符或下标来访问
09
this
.length = {
10
valueOf:
function
(){
11
return
m_elements.length;
12
},
13
toString:
function
(){
14
return
m_elements.length;
15
}
16
}
17
//公有方法,此方法使用得alert(alist)相当于alert(alist.toString())
18
this
.toString=
function
(){
19
return
m_elements.toString();
20
}
21
22
//公有方法
23
this
.add=
function
(){
24
m_elements.push.apply(m_elements,arguments);
25
}
26
27
//私有方法如下形式,这里涉及到了闭包的概念,接下来继续说明
28
//var add=function()或function add()
29
//{
30
//m_elements.push.apply(m_elements,arguments);
31
//}
32
}
33
34
var
alist=
new
List(1,2,3);
35
dwn(alist);
//=alert(alist.toString()),输出1,2,3
36
dwn(alist.length);
//输出3
37
alist.add(4,5,6);
38
dwn(alist);
//输出1,2,3,4,5,6
39
dwn(alist.length);
//输出6
属性和方法的类型
javascript里,对象的属性和方法支持4种不同的类型:private property(私有属性),dynamic public property(动态公有属性),static public property/prototype property(静态公有属性或原型属性),static property(静态属性或类属性)。私有属性对外界完全不具备访问性,可以通过内部的getter和setter(都是模拟);动态公有属性外界可以访问,每个对象实例持有一个副本,不会相互影响;原型属性每个对象实例共享唯一副本;类属性不作为实例的属性,只作为类的属性。
以下是例子:
01
///------------------------------------------------------------------------------------------------
02
//动态公有类型,静态公有类型(原型属性)
03
function
myClass()
04
{
05
var
p=100;
//private property
06
this
.x=10;
//dynamic public property
07
}
08
myClass.prototype.y=20;
09
//static public property or prototype property,动态为myClass的原型添加了属性,将作用于所有实例化了的对象,注意这里用到了prototype,这是一个非常有用的东东
10
//要想成为高级javascript阶段,prototype和闭包必须得理解和适当应用
11
myClass.z=30;
//static property
12
var
a=
new
myClass();
13
dwn(a.p)
//undefined
14
dwn(a.x)
//10
15
dwn(a.y)
//20
16
a.x=20;
17
a.y=40;
18
dwn(a.x);
//20
19
dwn(a.y);
//40
20
delete
(a.x);
//删除对象a的属性x
21
delete
(a.y);
//删除对象a的属性y
22
dwn(a.x);
//undefined
23
dwn(a.y);
//20 静态公有属性y被删除后还原为原型属性y
24
dwn(a.z);
//undefined 类属性无法通过对象访问
25
dwn(myClass.z);
原型(prototype)
这里只讲部分,prototype和闭包都不是几句话都能讲清楚的,如果这里可以给你一些启蒙,则万幸矣。习语"照猫画虎",这里的猫就是原型,虎是类型,可以表示成:虎.prototype=某只猫 or 虎.prototype=new 猫()。因为原型属性每个对象实例共享唯一副本,所以当实例中的一个调整了一个原型属性的值时,所有实例调用这个属性时都将发生变化,这点需要注意。
以下是原型关系的类型链:
01
function
ClassA(){
02
}
03
ClassA.prototype=
new
Object();
04
function
ClassB(){
05
}
06
ClassB.prototype=
new
ClassA();
07
function
ClassC(){
08
}
09
ClassC.prototype=
new
ClassB();
10
var
obj=
new
ClassC();
11
dwn(obj
instanceof
ClassC);
//true
12
dwn(obj
instanceof
ClassB);
//true
13
dwn(obj
instanceof
ClassA);
//true
14
dwn(obj
instanceof
Object);
//true
15
//带默认值的Point对象:
16
function
Point2(x,y){
17
if
(x)
this
.x=x;
18
if
(y)
this
.y=y;
19
}
20
//设定Point2对象的x,y默认值为0
21
Point2.prototype.x=0;
22
Point2.prototype.y=0;
23
//p1是一个默认(0,0)的对象
24
var
p1=
new
Point2();
//可以写成var p1=new Point2也不会出错,WHY
25
//p2赋值
26
var
p2=
new
Point2(1,2);
27
dwn(p1.x+
","
+p1.y);
//0,0
28
dwn(p2.x+
","
+p2.y);
//1,2
29
delete
对象的属性后,原型属性将回到初始化的状态:
30
function
ClassD(){
31
this
.a=100;
32
this
.b=200;
33
this
.c=300
34
}
35
ClassD.prototype =
new
ClassD();
//将ClassD原有的属性设为原型,包括其值
36
ClassD.prototype.reset =
function
(){
//将非原型属性删除
37
for
(
var
each
in
this
)
38
{
39
delete
this
[each];
40
}
41
}
42
var
d =
new
ClassD();
43
dwn(d.a);
//100
44
d.a*=2;
45
d.b*=2;
46
d.c*=2;
47
dwn(d.a);
//200
48
dwn(d.b);
//400
49
dwn(d.c);
//600
50
d.reset();
//删掉非原型属性,所有回来原型
51
dwn(d.a);
//100
52
dwn(d.b);
//200
53
dwn(d.c);
//300
继承
如果两个类都是同一个实例的类型,那么它们之间存在着某种关系,我们把同一个实例的类型之间的泛化关系称为继承。C#和JAVA中都有这个,具体的理解就不说了。
在javascript中,并不直接从方法上支持继承,但是就像前面说的,可以模拟。
方法可以归纳为四种:构造继承法,原型继承法,实例继承法和拷贝继承法。融会贯通之后,还有混合继续法,这是什么法,就是前面四种挑几种混着来~
以下例子涉及到了apply,call和一些Array的用法:
构造继续法例子
01
//定义一个Collection类型
02
function
Collection(size)
03
{
04
this
.size =
function
(){
return
size};
//公有方法,可以被继承
05
}
06
07
Collection.prototype.isEmpty =
function
(){
//静态方法,不能被继承
08
return
this
.size() == 0;
09
}
10
11
//定义一个ArrayList类型,它"继承"Collection类型
12
function
ArrayList()
13
{
14
var
m_elements = [];
//私有成员,不能被继承
15
m_elements = Array.apply(m_elements, arguments);
16
//ArrayList类型继承Collection
17
this
.base = Collection;
18
this
.base.call(
this
, m_elements.length);
19
20
this
.add =
function
()
21
{
22
return
m_elements.push.apply(m_elements, arguments);
23
}
24
this
.toArray =
function
()
25
{
26
return
m_elements;
27
}
28
}
29
30
ArrayList.prototype.toString =
function
()
31
{
32
return
this
.toArray().toString();
33
}
34
//定义一个SortedList类型,它继承ArrayList类型
35
function
SortedList()
36
{
37
//SortedList类型继承ArrayList
38
this
.base = ArrayList;
39
this
.base.apply(
this
, arguments);
40
this
.sort =
function
()
41
{
42
var
arr =
this
.toArray();
43
arr.sort.apply(arr, arguments);
44
}
45
}
46
//构造一个ArrayList
47
var
a =
new
ArrayList(1,2,3);
48
dwn(a);
49
dwn(a.size());
//a从Collection继承了size()方法
50
dwn(a.isEmpty);
//但是a没有继承到isEmpty()方法
51
//构造一个SortedList
52
var
b =
new
SortedList(3,1,2);
53
b.add(4,0);
//b 从ArrayList继承了add()方法
54
dwn(b.toArray());
//b 从ArrayList继承了toArray()方法
55
b.sort();
//b 自己实现的sort()方法
56
dwn(b.toArray());
57
dwn(b);
58
dwn(b.size());
//b从Collection继承了size()方法
原型继承法例子
01
//定义一个Point类型
02
function
Point(dimension)
03
{
04
this
.dimension = dimension;
05
}
06
//定义一个Point2D类型,"继承"Point类型
07
function
Point2D(x, y)
08
{
09
this
.x = x;
10
this
.y = y;
11
}
12
Point2D.prototype.distance =
function
()
13
{
14
return
Math.sqrt(
this
.x *
this
.x +
this
.y *
this
.y);
15
}
16
Point2D.prototype =
new
Point(2);
//Point2D继承了Point
17
//定义一个Point3D类型,也继承Point类型
18
function
Point3D(x, y, z)
19
{
20
this
.x = x;
21
this
.y = y;
22
this
.z = z;
23
}
24
Point3D.prototype =
new
Point(3);
//Point3D也继承了Point
25
26
//构造一个Point2D对象
27
var
p1 =
new
Point2D(0,0);
28
//构造一个Point3D对象
29
var
p2 =
new
Point3D(0,1,2);
30
dwn(p1.dimension);
31
dwn(p2.dimension);
32
dwn(p1
instanceof
Point2D);
//p1 是一个 Point2D
33
dwn(p1
instanceof
Point);
//p1 也是一个 Point
34
dwn(p2
instanceof
Point);
//p2 是一个Point
以上两种方法是最常用的。
实例继承法例子
在说此法例子之前,说说构造继承法的局限,如下:
1
function
MyDate()
2
{
3
this
.base = Date;
4
this
.base.apply(
this
, arguments);
5
}
6
var
date =
new
MyDate();
7
//undefined,date并没有继承到Date类型,所以没有toGMTString方法
8
alert(date.toGMTString);
核心对象的某些方法不能被构造继承,原因是核心对象并不像我们自定义的一般对象那样在构造函数里进行赋值或初始化操作换成原型继承法呢?,如下:
1
function
MyDate(){}
2
MyDate.prototype=
new
Date();
3
var
date=
new
MyDate();
4
//'[object]'不是日期对象,仍然没有继承到Date类型!
5
alert(date.toGMTString);
现在,换成实例继承法:
01
function
MyDate()
02
{
03
//instance是一个新创建的日期对象
04
var
instance =
new
Date();
05
instance.printDate =
function
(){
06
document.write(
"<p> "
+instance.toLocaleString()+
"</p> "
);
07
}
08
//对instance扩展printDate()方法
09
return
instance;
//将instance作为构造函数的返回值返回
10
}
11
var
myDate =
new
MyDate();
12
//这回成功输出了正确的时间字符串,看来myDate已经是一个Date的实例了,继承成功
13
dwn(myDate.toGMTString());
14
//如果没有return instance,将不能以下标访问,因为是私有对象的方法
15
myDate.printDate();
拷贝继承法例子
01
Function.prototype.extends =
function
(obj)
02
{
03
for
(
var
each
in
obj)
04
{
05
this
.prototype[each] = obj[each];
06
//对对象的属性进行一对一的复制,但是它又慢又容易引起问题
07
//所以这种"继承"方式一般不推荐使用
08
}
09
}
10
11
var
Point2D =
function
(){
12
//……
13
}
14
15
Point2D.extends(
new
Point())
16
{
17
//……
18
}
这种继承法似乎是用得很少的。
混合继承例子
01
function
Point2D(x, y)
02
{
03
this
.x = x;
04
this
.y = y;
05
}
06
07
function
ColorPoint2D(x, y, c)
08
{
09
Point2D.call(
this
, x, y);
//这里是构造继承,调用了父类的构造函数
10
//从前面的例子看过来,这里等价于
11
//this.base=Point2D;
12
//this.base.call(this,x,y);
13
this
.color = c;
14
}
15
ColorPoint2D.prototype =
new
Point2D();
//这里用了原型继承,让ColorPoint2D以Point2D对象为原型
- JavaScript面试后的反思
- 面试后的反思
- 关于某次面试后的反思
- FAE面试后的自我反思
- 阿里巴巴暑期实习面试后的总结与反思
- UITableView性能优化-一次面试后的反思总结
- 第一次面试的反思
- 毕业后的反思
- 考试后的反思
- 检讨后的反思
- 一次面试失败的反思
- 灾难后中国文化的反思
- 寒假几天后的反思
- “家园”完成后的反思
- 看博客后的反思
- 离职一年后的反思
- 线程“死亡”后的反思
- 创业后的一些反思
- eclipse安装最新版SVN 1.8
- C#调用C++方法,C#使用c++方法返回类或结构体
- Windows GDI和GDI+ 区别和联系简介
- linux svn 代码迁移 checkout
- 驱动分层-挂载设备对象示例
- JavaScript面试后的反思
- Ecmail Call to a member function get_users_count() on a non-object解决方法
- java之static 和 final学习
- C++ 多继承和虚继承的内存布局
- 笔记本中WIN7系统如何快速关闭或打开无线网络
- hadoop-eclipse-plugin-2.2.0.jar放到eclipse的plugins文件夹后,eclipse中没有map/reduce项
- php静态页面获取session的值
- Struts2的工作原理(源码分析)
- Scala + Play + Sbt + Protractor = One Build