一个 Java 程序员对 JavaScript 的理解

TLDR 本文是一个 Java 程序员初学 JavaScript 的记录

知道吗?《重构》要出第二版了

是吗,和第一版有啥区别?

语言从 Java 改成 JavaScript 了

我对 Java 挺熟的,但是 JavaScript 只写过一些前端脚本,用过 nodejs 但也就是复制黏贴做点 CRUD, 这个水平去看书恐怕不行。

那就去学呗, JavaScript 那么流行,学了不亏。

可我总觉得 JavaScript 很复杂,应该说很乱,各种各样的坑,Java 就很工整.

你说的工整是什么意思?

JavaScript 是脚本语言啊,各种变量,函数散落在各处,看的头晕。

这方面语言本身是这样设计的,函数是一等公民,不像 Java,函数必须被定义在类中,这大概就是你觉得 Java “工整“ 的原因吧。

函数是一等公民我还能理解,可是 JavaScript 为啥没有类啊,不对,好像 es6 有 class,但是还要转换,好麻烦。

慢慢来,先别考虑 es6,你要类来干嘛?

当然是创建对象啊。

在 JavaScript 中,创建对象很容易啊

const person = {
  firstName: 'foo'
}
console.log(person.firstName);

额,你这只是属性没有方法啊, 这样的对象是没有灵魂的

要方法也行,function 也是一个对象,我们可以把函数直接赋值给 person 对象的一个属性

const person = {
  firstName: 'foo',
  hello: function() { console.log("hello world") }
}
person.hello();

额,看起来和 Java 的对象很相似了,但是,下次我需要另一个 person,总不能每次都复制一大段代码吧。

办法当然有,不过还是慢慢来,目前我们知道 JavaScript 可以很方便的创建对象,但是没有类,那么我们可以手动创建一个功能和类类似的东西。

等等,让我想想,如果是 Java 的话,除了 new 一个对象外,得到对象的方式还可以是工厂类,工厂类一般都使用静态方法,所以使用对象替换也完全可行。

没错,我们可以创建一个 PersonFactory 来创建各种 person 对象,顺便说一句,Java 的静态方法实在是太不 OO 了,直接使用对象更合理。

function PersonFactory(last_name) {
  let person = {}
  person.last_name = last_name
  person.hello = function (last_name) {
    console.log(`hello, I am ${this.last_name}.`)
  }
  return person
}

person = PersonFactory('foo')
person.hello();

看着还行,但是比起 Java 的原生 Class 还是太麻烦了。

淡定,这只是一个演示,JavaScript 有原型继承,可以使用 prototype 来实现,这才是最常用的方式(上面的写法也很浪费内存,因为方法不能共享)。

Person.prototype.hello = function(last_name) {
  console.log(`hello, I am ${this.last_name}.`)
}
function Person(last_name) {
  let person = Object.create(Person.prototype)
  person.last_name = last_name
  return person;
}
person = Person("foo")
person.hello()

原来是这样,我明白了,其实这样已经实现了 Java 的 class 功能了,难怪一开始你说 es6 的 class 只是语法糖而已,那 class 的写法是?

使用 es6 的 class 的话就是这样:

class Person {
  constructor(last_name) {
    this.last_name = last_name
  }
  hello(){
    console.log(`hello, I am ${this.last_name}.`)
  }
}
const person = new Person('foo')
person.hello()

这样就和 Java 基本一样了。等等,这个 constructor 是啥啊?

就是构造器,看来我们步子迈的有点大了,我们先看看上面的例子中, person 的构造器是什么:

function Person(last_name) {
  this.last_name = last_name
}
var person = new Person("foo")
console.log(person.constructor)

看到没,就是 Person 函数,可见,构造器就是构造函数的引用,它告诉我们 person 对象是哪儿来的。我们可以通过 instanceof 操作符来判断对象和构造器函数的关系:

console.log(person instanceof Person)

明白了,对了,上面的代码里还有一个 this, 听说 javascript 里的 this 非常难以理解,到底是怎么回事呢?

嗯, 其实一点也不难,我们先看看 Java 中的 this 吧。

public class Person{
  private String lastName;
  public Person(String lastName) {
    this.lastName = lastName;
  }
  public void hello() {
    System.out.println("hello, I am " + lastName);
  }
}
Person person = new Person("foo")
person.hello();

这个例子很简单,带 this 的 lastName 是私有属性, 不带 this 的 lastName 是传入的参数,这看似是一个可以死记硬背的知识点。

但是我们可以换个角度看看,在 Java 中,可以调用方法的实例必然是 person 对象,所以 this 肯定是指向 person 对象的。 而在 JavaScript 中,函数是独立存在的,因此调用方和函数可以没有任何关系。

不过原理是类似的,那就是 this 指向的就是方法(函数)的调用方。

我们可以看看下面这个例子:(first name 的 例子)

function Person(last_name) {
  console.log("My name is: " + this.first_name + " " + this.last_name);
}
Person.prototype.hello = functon() {
  console.log("hello, my name is" + this.first_name + " " + this.last_name);
}
var person = new Person("foo")
person.first_name = "bar"
person.hello();

这个例子可以说明,this 和它“所在”的函数是没有任何关系的,在 function Person 中,根本没有定义 firstname 这个属性,这个属性是我们定义在 person 对象中的。 当 hello 函数被执行时, this 就是 person 对象,所以 this.first_name 就是 person 对象 firstname 属性。 当然 JavaScript 这样的动态语言,肯定不止看上去那么简单,不过这个解释对于理解大部份情况的代码已经足够了。

现在看来 JavaScript 也不是那么 "神奇" 和难以理解了,看来我以前对于面向对象的理解太片面了。

其实刚才我教你的都是很基础的部分,不过既然你已经把思路从 Java 中的 Class 转到 javaScript 的 object 了,那就是一个很好的开始了。