JavaScript经验整理
接触Web开发也已经有一段时间了,对javascript的认识也比以前有了更加深入的认识了,所以觉得应该整理一下。
一、JavaScript不支持函数(方法)的重载,用一个例子证明如下:
function add(number)
{
alert('hello');
}
function add(number1, number2)
{
alert('world');
}
此时调用add(1),会弹出'world',而不是'hello',由此现象说明写在文档后面的方法会覆盖掉前面的方法,所以有一个我认为暂时是正确的原则,就是如果方法名相同,则以后面对方法为主。
二、JavaScript中函数(方法)就是一个对象,这里就不解释了,看一个例子比较直观
function add(number)
{
alert('hello');
}
是等价于下面这种写法的
var add = new Function("number","alert('hello')");
以此类推
function add(number1, number2)
{
alert('world');
}
等价于
var add = new Function("number1","number2","alert('world')");
这里一和二都是一样的,只是写法不同,底层是一样的,所以可以把JavaScript的不支持重载的现象按照面向对象的概念来理解:首先add对象指向第一个Function对象,而后引用的对象改变了,指向了第二个新的Function对象了。示意图如下
所以可以得出一个这样的结论:JavaScript中的每个函数function都是new出来的Function对象。
三、如果在调用某个JavaScript函数的时候,没有给函数中的参数传值,则参数为undefined,例子如下:
function add(number)
{
alert(number);
alert(number + 30);
}
当在触发处调用add()的时候,显然没有传入参数值,此时先弹出undefined(未定义),
然后弹出NaN(不是一个数Not A Number)
四、arguments是每个函数的内置对象,应用这个内置对象可以模拟面向对象的重载功能,例如
function sum()
{
if(1 == arguments.length)
{
alert(arguments[0]);
}
else if(2 == arguments.length)
{
alert(arguments[0] + arguments[1]);
}
else if(3 == arguments.length)
{
alert(arguments[0] + arguments[1] + arguments[2]);
}
}
调用如下:
sum(1); => 1
sum(1, 2); => 3
sum(1, 2, 3); => 6
五、由上面的例子又可以得到,JavaScript的函数就是Function的对象,函数都有一个length属性,表示函数接收参数的个数。证明如下:
function add() {.....}
function minus(num) {....}
function multiply(num1, num2) {.....}
调用如下:
alert(add.length) => 0
alert(minus.length) => 1
alert(multiply.length) => 2
六、JavaScript中有五种原始值,可以理解为原生数据类型(这里仅仅是值JavaScript的,不同于3GL语言),它们分别是:Undifined,Null,Boolean,Number,String
(1)Undefined数据类型的值只有一个:undefined,这里要注意一点就是,对于JavaScript中的变量,如果只声明,而没有赋值,则该变量的值为undefined。
例如:var s; alert(s); 此时弹出的是undefined
(2)Null数据类型的值只有一个:null
(3)Boolean数据类型的值有两个:true和false
(4)任何整数或者浮点数都是Number数据类型中的值,另外typeof(..)函数会返回参数的类型名称,例如:
var s; alert(typeof(s)); 弹出undefined
var a = 1; alert(typeof(a)); 弹出number
var b = 'hello'; alert(typeof(b)); 弹出string 双引号表示原始类型在栈里
而var c = new String("hello"); alert(typeof(c)); 弹出object,是在堆中的对象了
(5)一个函数如果不返回值,则返回'undefined',typeof(..)返回的值是字符串类型的,证明如下:
function aa() {..//里面没有return出值..} alert(aa()); 会弹出undefined
function bb() {....; return; } alert(bb);会弹出bb函数体{}中的所有源代码内容
(6)使用没有声明,并且没有赋值的变量会报错,例如,
var s; alert(s2); 因为s2没有声明也没有赋值,所以会报错
alert(typeof(s)); 弹出undefined
alert(typeof(s2)); 尽管s2没有声明也没有赋值,但会弹出undefined
由此可以归纳出,typeof(..)的返回值有五个:undefined,boolean,number,string,object;
(7)null与undefined的关系:undefined实际上是从null派生而来的,证明如下,
alert(undefined == null); 会弹出true
(8)在函数体内加不加var的区别:如果在函数体的内部,变量不加var,则默认该变量为全局变量,证明如下:
function test()
{
var s = 'hello';
}
test();
alert(typeof(s));此时会返回undefined
然而
function test()
{
s = 'hello';
}
test();
alert(typeof(s));会弹出string
alert(s);会弹出hello
由此可知,对于函数定义中的变量来说,加var表示局部变量,不加var表示全局变量。
(9)强制类型转换有三种:Boolean(value),Number(value),String(value) [这里可以看作函数]
例如,
var s = Boolean(null); alert(s); 会弹出false
var s = Boolean(""); alert(s);会弹出false
var s = Boolean("a"); alert(s);会弹出true
new Boolean(),new String("..."),new Number("...")都是object类型的
(10)在JavaScript中,Object是所有类的父类,类似于高级语言java
var s = new Object();
alert(s); 弹出[object Object]
alert(typeof(s)); 弹出object
alert(s.propertyIsEnumerable("prototype"));弹出false,这里是判断s中的属性是否可以枚举出来
遍历window对象中的所有属性
for(var temp in window)
{
alert(temp);
}
delete操作和JavaScript中的晚绑定:
var o = new Object();
o.name = "scott";
alert(o.name); //会弹出scott
delete(o.name); //删除晚绑定的name属性
alert(o.name); //会弹出undefined
七、JavaScript的面向对象的特性:
(1)JavaScript是基于ECMAScript的,构造对象时可以直接用名字,例如
var object = new Object; <=> var object = new Object();
var s = new String; <=> var s = new String();
var date = new Date; <=> var date = new Date();
尽管如此,还是建议加上括号,增加可读性!!!!
(2)JavaScript中的数组,长度可以动态的增加,类似于java中的ArrayList,例如
var array = new Array(); <=> var array = [ ];
array.push(1);
array.push(2);
array.push(3);
alert(array.length); //弹出3
这里也可以用var array = [1,2,3]; alert(array.length); //弹出3
(3)数组对象有一个方法sort(),可以对数组进行升序排序,例如
var array = [3,2,1];
array.sort();
alert(array); //弹出[1,2,3]
咋一看,很爽,但是
var array = [1,3,21];
array.sort();
alert(array); //弹出[1,21,3]
原因就在于:sort()方法对数组进行排序的时候,首先将数组中的每一个元素都转化为字符串,即调用每个元素的toString()方法,然后用字符串的ASCII码进行比较! 所以'21' < '3'很正常!
另外,sort()可以接受一个排序的函数作为参数,根据参数排序,类似于java中的compare()方法
例如,
function compare(num1, num2)
{
var temp1 = parseInt(num1);
var temp2 = parseInt(num2);
return temp1 - temp2;
}
var array = [3,1,21];
array.sort(compare);
alert(array); //弹出[1,3,21]
这里还可以用匿名函数的方法,类似于java中的匿名类
array.sort(function(num1, num2) {
var temp1 = parseInt(num1);
var temp2 = parseInt(num2);
return temp1 - temp2;
});
(4)对象的属性方式:[1]早绑定 [2]玩绑定
所谓早绑定,就是在对象生成之前绑定好的属性,例如,
var object = new Object();
object.name = "scott";
alert(object.name);
所谓晚绑定,就是对生成好的对象增加或减少它的属性,例如,
object.hello = function() { alert('welcome'); }
object.hello(); //此时会调用object的hello方法,弹出welcome
delete object.hello(); //删除方法
object.sayName = function() { alert(this.name); } //这里this指的是object对象,类似于java一样
object.sayName(); //此时会弹出scott
object.sayName = function(name) { //绑定一个传参数的方法
this.name = name;
alert(this.name);
}
object.sayHello('tiger'); //此时弹出tiger
=================================================================
一、用工厂方式定义对象(类),这里是参照java的设计模式的工厂模式写的,例子如下
function createObject()
{
var object = new Object(); //这里请加上var,因为是方法体内部的局部变量
object.username = "scott";
object.password = "tiger";
object.get = function()
{
alert("username: " + this.username + "\npassword: " + password);
}
return object;
}
调用如下,
var o = createObject();
o.get(); //此时会弹出username: scott
// password: tiger
该方法的好处是凡是学要对象了,不需要自己new一个出来,直接可以通过createObject生成对象,具体的细节都被封装起来了~
上面的工厂方式固然有优点,但是生成的对象属性都被默认写死成scott和tiger了,所以可以改良成如下带参数的工厂方法,例子如下,
function createObject(username, password)
{
var object = new Object();
object.username = username;
object.password = password;
object.get = function()
{
alert("username: " + this.username + "\npassword: " + password);
}
return object;
}
调用如下,
var o = createObject("scott","tiger");
o.get();
该方法的好处比起一楼的方式,就是更灵活的控制了要生成的对象。
然而对于以上两种工厂方式,方法get()定义在createObject()函数的内部,从性能上考虑,没创建一个对象,就要生成一个get的function对象,但是在高级语言java中所有的对象都共同的用同一个方法,需要注意到是在JavaScript中get方法不是如此,工厂每次生成对象的时候,都会为get再生成一次相同的对象,比较消耗内存,因此,可以将get方法的定义放在createObject之外。例如,
function get()
{
alert("username: " + this.username + "\npassword: " + this.password);
}
function createObject()
{
var object = new Object();
object.username = "scott";
object.password = "tiger";
object.get = get;
return object;
}
调用方法一样的,有点就是优化了性能,功能还是一样~
二、构造函数的方式
function Person()
{
this.username = "scott";
this.password = "tiger";
this.get = function()
{
alert("username: " + this.username + "\npassword: " + this.password);
}
}
用这种方法定义比较直观,与Java中生成对象很类似了,调用如下,
var person = new Person();
person.get();
注解:当调用new Person()的时候,执行流程跳到了函数中去了,在执行第一行代码之前,会生成一个对象,而this则会指向该对象,虽然没有return该对象,但是此构造函数隐式地将对象return出去了;
另外,传递参数也是类似的,内部函数的定义也可以改良从而降低对内存的消耗,将它拿到构造函数外面单独定义,所以可以改写成如下,
function get()
{
alert("username: " + this.username + "\npassword: " + this.password);
}
function Person(username, password)
{
this.username = username;
this.password = password;
this.get = get;
}
以上这种方式还是不够直观,有另一种解决方案如下,
原型方式创建类:prototype
function Person(){ }
Person.prototype.username = "scott";
Person.prototype.password = "tiger";
Person.prototype.get = function()
{ alert("username: " + this.username + "\npassword: " + this.password); }
调用方法:
var p = new Person();
p.get();
但是这里使用prototype方法生成对象,不能创建的时候初始化值,必须等对象创建出来以后,对属性修改。
var person1 = new Person();
var person2 = new Person();
person1.username = 'hello';
person1.get(); //hello tiger
person2.get(); //scott tiger
JavaScript中的字符串也是不可变的,与java机制中一样,属于final类型的,字符串的拼接一会生成新对象
如果Person.prototype.username = new Array(); //如下面一楼贴出的图片person1和person2会共享scott
person1.username.push("scott");
person1.get(); //scott
person2.get(); //scott
混合的构造函数与原型方式,属性采用构造函数的方式定义,方法采用原型方式定义(这里比较推荐这种方式)
可以避免上面的共享属性的弊端,例如,
function Person(username,password)
{
this.username = username; //这里如果this.username = new Array(); 则新对象不会共享username
this.password = password;
}
Person.prototype.get = function()
{
alert("username: " + this.username + "\npassword: " + this.password);
}
调用方法:
var p = new Person("scott","tiger");
p.get();
另外还有一种方式:动态的原型方式
function Person()
{
this.username = "scott";
this.password = "tiger";
if(typeof Person.flag == "undefined") //或者Person.flag == undefined
{
Person.prototype.get = function()
{
alert("username: " + this.username + "\npassword: " + this.password);
}
Person.flag = true;
}
}
下面来看看用JavaScript模拟java的继承特性
(1)继承的第一种方式:对象冒充
function Parent(username)
{
this.username = username;
this.hello = function()
{ alert("username: " + this.username); }
}
function Child(username,password)
{
this.method = Parent;
this.method(username);
delete this.method;
this.password = password;
this.world = function()
{ alert("password: " + this.password); }
}
根据上图,和带下划线的代码段,method属性作为桥梁引用Parent对象,调用Parent的构造函数后,删除了该属性!!
注意如果吧带下划线的代码段替换为Parent(username);是不行的,因为这样的话Parent中的hello()方法不能继承下来~
调用上面的继承
var parent = new Parent('father');
var child = new Child('son','scott');
parent.hello(); //father
child.hello(); //son
child.world(); //scott
由此可见继承机制确实实现了!!!!
(2)继承的第二种方式:call()方法
这里先介绍call()方法:每个function,即Function对象都有call()这个方法,call(param1,param2,...),
param1表示调用call()方法的函数内部的this,param2,...对应调用call()方法的函数中的参数列表,例如,
function test(str)
{
alert(this.name + " " + str);
}
var obj = new Object();
obj.name = 'scott';
test.call(obj,'tiger'); //这里将obj对象赋给test中的this,弹出scott tiger
了解了call()的功能以后,我们来用call()实现继承机制,例子如下:
function Parent(username)
{
this.username = username;
this.hello = function()
{
alert(this.username);
}
}
function Child(username, password)
{
Parent.call(this,username);
this.password = password;
this.world = function()
{ alert(this.password); }
}
调用方法类似,这里就省略了。。。
(3)继承的第三种方式:apply()方式
这里对比一下apply()和call()
call(parm1,parm2,...,parmN) 多个参数
apply(parm1,new Array()) <=> apply(parm1,[parm2,...,parmN]) 两个参数
归纳一下,apply方法的第一个参数和call一样,第二个参数是call后面参数组成的一个数组
下面用apply()来实现继承的机制,如下:
function Parent(username)
{
this.username = username;
this.hello = function()
{ alert(this.username); }
}
function Child(username,password)
{
Parent.apply(this,new Array(username)); //等价于Parent.apply(this,[username]);
this.password = password;
this.world = function()
{ alert(this.password); }
}
(4)继承的第四种方式:原型链方式
function Parent() { }
Parent.prototype.hello = "hello";
Parent.prototype.sayHello = function()
{ alert(this.hello); }
function Child() { }
Child.prototype = new Parent();
Child.prototype = "world";
child.prototype.sayWorld = function()
{
alert(this.world);
}
调用一下:
var child = new Child();
child.sayHello(); //hello
child.sayWorld(); //world
(5)继承的第五种方式:混合方式(最佳实践,推荐使用)
function Parent(hello)
{
this.hello = hello;
}
Parent.prototype.sayHello = function()
{ alert(this.hello); }
function Child(hello,world)
{
Parent.call(this,hello);
this.world = world;
}
Child.prototype = new Parent();
Child.prototype.sayWorld = function()
{ alert(this.world); }
调用一下:
var child = new Child('hello','world');
child.sayHello(); //hello
child.sayWorld(); //world
最后举个例子来应用JavaScript的继承机制的应用
function Shape(edge) //一个抽象的形状
{
this.edge = edge;
}
Shape.prototype.getArea = function() //获取形状的面积,类似于接口中的方法,这里先返回-1
{ return -1; }
Shape.prototype.getEdge = function() //返回形状的边有多少条
{ return this.edge; }
下面定义子类,三角形
function Triangle(bottom,height) //三角形,出入参数为底边和高
{
Shape.call(this,3);
this.bottom = bottom;
this.height = height;
}
Triangle.prototype = new Shape();
Triangle.prototype.getArea = function() //返回三角形的面积,覆盖父类的方法
{ return 0.5 * this.bottom * this.height; }
调用三角形的对象和方法:
var triangle = new Triangle(10,4);
alert(triangle.getEdge()); //3
alert(triangle.getArea()); //20
下面再定义一个四边形
function Rectangle(bottom, height)
{
Shape.call(this,4);
this.bottom = bottom;
this.height = height;
}
Rectangle.prototype = new Shape();
Rectangle.prototype.getArea = function()
{ return this.bottom * this.height; }
调用四边形的对象和方法
var rectangle = new Rectangle(10,20);
alert(rectangle.getEdge()); //4
alert(rectangle.getArea()); //200