在现代网络应用的开发中,JavaScript可以说是一门无可替代的语言(即使是新型语言typescript也需要编译成为JavaScript才可以运行),无论web前端、移动端、甚至服务端都需要用到JavaScript,这篇文章就着JavaScript的一些特性做一些整理。
作为一个java出身的开发人员,我第一次接触js是由于ionic框架的研究,当时我有一种“哇塞,这个世上居然有那么方便的语言”的想法,或许也是因为js过于便捷,以至于偶尔会听到一些贬义的议论,认为js只是个脚本,会js没啥用(其实对于这样的认知我也就想笑笑,除非你研究的足够底层,否则你能做的,js都能做)。言归正传,js之所以如此的方便,莫过于两大绝对的特性:弱类型定义& 基于对象。
弱类型定义应该很好理解,与java这些需要用明确的基本常量或者类来定义变量不同,js可以很简单的使用var关键字去定义任何变量,这从一定的角度上可以达到方便的效果,而js的另一个特性,可能是很多初学者不会去注意的一点,就是其基于对象的特性。
我们知道一般的面向对象语言,如java、c++,都需要开发者去编写一系列的类,然后由类去构造相应的对象,而js则不同,他没有类这个概念,他所创造的所有变量都是对象,无论基本变量、函数、DOM或其他种种,一切都是对象,而所有的对象下面又有一系列的键值对,所有的值依旧是个对象,以此类推,在对象的赋值的时候可以直接用对象字面量(类似json)的方式来产生新对象:
var object1 = {
name: 'xxx',
func : function() {
},
child:{
name:'yyy'
}
}
而但凡是获取对象,都可以用两种方式,其一是下标点调用,其二是数组型调用:
object1.name;
object1['name'];
尤其是第二种方案,可以给代码的带来抽象化的便捷。
而对于函数类型的变量,还可以用多种方式调用:
function f1(a) {
console.log(a)
}
f1('x');
f1.call(this, 'x')
f1.apply(this, ['x'])
直接用括号调用,或者用call和apply传入执行的对象。
优势
由JS一切基于对象的性质带来的方便也是数不胜数,我们可以随意举出几个例子:
1.回调函数
在做android开发的人应该都不会陌生java的回调机制,在java中,想要进行回调,必须定义一个用于回调的接口,如我们非常常见的OnClickListener接口,然后在传入回调的过程中传入接口的实现类的对象,再有被调用方去回调这个接口的回调方法,如下面这个简单的例子:
@Test
public void testCallBack() {
this.doCallBack(new ICallBack() {
@Override
public void call() {
System.out.println("testtesttest");
}
});
}
public void doCallBack(ICallBack callBack) {
new Thread(new Runnable() {
@Override
public void run() {
callBack.call();
}
}).start();
}
public interface ICallBack {
public void call();
}
但是相比之下js就非常简单,比如说我们非常熟悉的click事件回调:
$('.cred_identity_success').click(function() {
pr(Cred_identityList).changeStatus(this.getAttribute('titleId'), this.getAttribute('userId'), statusSuccess);
});
我们可以非常直接地把回调函数传入进去,然后在被调用方直接使用传入的参数即可,这一切归功于JS将函数也看成一个对象。
2.抽象赋值和调用
前面已经说过,js的对象可以以object['name']的形式来获取,因此在抽象调用的过程中可谓是非常简单,比方说我在html页面中需要由用户操作获取js中的一些数据,相比起对于不同操作的if else判断,直接将事件中取出的世界对应的自定义名字,并以此来获得数据会更加简单:
var events = {
event1: function() {
//...
},
event2: function() {
//...
},
event3: function() {
//...
}
}
function(eventName) {
var event = events[eventName];
if (typeof event === 'function') {
event();
// 或者call和apply
}
}
这种类似于java中反射的措施,却比反射要方便的多。
“类”化
1.继承
上文说了JS本身没有类这个概念,因此理论上JS应该是不支持继承的,但是JS也有自己的继承方式,比如说它依靠prototype实现继承,实则是依靠了对象内置的一个__proto__变量来完成,__proto__变量则存储了所有用prototype实现继承的所有参数,在调用js对象的变量时,会先去检查直接变量中是否有这个参数,如果没有则会去检查__proto__变量中是否有这个参数,依次类推,可以百度js原型链来获取完整的解释。
换句话说,js之所以能实现继承,也是因为有一个特殊变量(也是一个对象)。
2.构造函数
虽然说js没有类这个概念,但是他可以通过特殊的方法来实现对象的统一构造,所采用的是一个new 构造符:
function Class1() {
this.name = "sss";
this.say = new function() {
}
};
var obj1 = new Class1();
new关键字的原理也非常简单,它会将this指向新生成的这个对象,因此在上面这个函数中所有对this的操作则变成了对新对象的构造。
但是这样的构造函数某些情况下并不如意,尤其是我们如果需要设置私有变量的时候,JS的Module模式可以很简单的完成公、私有变量的设计:
var Module = function() {
var self = {};
self.name = "xxx";
self.pass = "yyy";
self.setName = function(name) {
self.name = name;
};
self.setPass = function(password) {
self.password = password;
};
self.getName = function() {
return self.name;
};
return {
setName:self.setName,
getName:self.getName
}
}
但是有一点,就是用这种方式去产生构造函数是没有办法继承原型的,解决的方式其实有很多,这也对应了继承的不同方案,在《JavaScript模式》一书中对于不同的构造函数和继承有着详细的讲解,我们这里简单说两种解决原型继承的方式。
第一种:这种方式是我自己想出来的,既然原型的原理是给对象设置__proto__对象,那么我们直接在Module模式的构造函数中加上一条:
var result = this.__proto__ || {};
result.setName = self.setName;
result.getName = self.getName;
return result;
用这种方式就可以完成原型的继承了。
第二种:利用父类构造函数,将self设置为父类产生的对象
两种方式都能解决,但是由于JS语言本身并不支持类的存在,所以也没用真正意义上完美的构造函数和继承方案,可以通过对上述《JavaScript模式》一书的阅读加深理解,本文也是基于对js本质性质的解释来减少阅读过程中的负担,个人认为虽然可能很难罗列出最优的构造函数和继承,但是可以通过对js对象的理解,让自己在遇到某一种构造函数和继承时候很容易辨别其利弊并在开发中加以规范。
前文罗列了一大堆基于对象的优点,体现在包括方便在内的方方面面,但是我们也可以发现JS在其方便之余产生的许多麻烦。
劣势
1.传参不检查
这应该是个非常明显的麻烦,比如JS的回调函数,当用到回调的时候如果贸然调用而不进行参数检查,那当传入参数不是一个函数的时候自然会报错,解决的方案也比较简单:
function aaa(x, y) {
if (typeof x === 'function') {
throw new Exception1('sssss');
} else if (isNaN(y)) {
throw new Exception2('rrrr',1);
}
}
利用typeof关键字对传入参数进行检查。
2.对对象的随意修改
初学JS者可能经常写这种代码:
for(int i = 0; i < list.length; i++) {
list[i].index = i;
list[i].οnclick=function() {
//....
var index = this.index;
}
}
诚然这非常的方便,给数组设置点击事件的时候,经常需要获取到点击目标的下标,这种随意加上一个参数的行为显然十分方便,但是同时也十分危险,因为如果list数组中元素原本就有index变量的时候,这样的操作自然就会产生问题且非常难以检查,在团队项目合作中这就是一个极大的弊端,《编写可维护的JavaScript》一书对于这个内容也有详细说明,章节名“不是你的对象不要动”,在这里除了呼吁不要乱动不是自己定义的方法之外,还呼吁对于必要操作采用继承或组合的形式来完成,打个比方说,jQuery的目的是方便dom操作,但是它并不直接修改DOM对象,而是给DOM对象包了一层jQuery对象的包装,所有的方法和参数定义在这里。
JS中类似的规范性弊端非常多,当项目扩大至需要团队开发一个前端项目的时候,这种弊端自然而然地显现在了开发人员面前,小打小闹的局部逻辑无法架构其庞大的项目,这或许是JS的学习容易遇到瓶颈的一大重要原因,虽然可以使用框架来一定程度上解决这种情况,但是自己对于规范性的理解也是突破瓶颈的重要方式,这里强烈推荐三本书《JavaScript模式》、《编写可维护的JavaScript》、《JavaScript设计模式》,这三本书个人认为难度逐一增加,在阅读过程中去体会优秀代码的设计,或者结合阅读框架源码,理解JS基本特性,会变得非常重要。