这篇文章将从Java开发人员的角度介绍Java语言,重点介绍两种语言之间的区别以及常见的难点。 我们将介绍以下内容:
- 仅对象,无类
- 功能就是价值
- “ this”关键字
- 经典与原型继承
- 构造函数与构造函数
- 闭包vs Lambdas
- 封装和模块
- 范围和吊装
为什么在Java世界中使用Javascript?
许多Java前端开发工作都是使用基于Java / XML的框架(如JSF或GWT)完成的。 框架开发人员本身需要了解Javascript,但原则上应用程序开发人员则不需要。 但是现实是:
- 对于在Primefaces(JSF)等中进行自定义组件开发,了解Javascript和jQuery非常重要。
- 在GWT中,至少集成一些第三方Javascript小部件是常见且具有成本效益的。
最终结果是,即使使用Java框架,通常也需要Javascript至少完成前端工作的最后5%至10%。 同时,它也开始与Angular一起越来越多地用于多语言企业开发。
好消息是,除了我们将要了解的一些陷阱之外,对于Java开发人员来说,JavaScript是一种非常易学的语言。
仅对象–无类
关于Javascript的最令人惊讶的事情之一是,尽管它是一种面向对象的语言,但没有类(尽管新的Ecmascript 6版本将包含它们)。
以这个程序为例,该程序初始化一个空对象并设置两个属性:
// create an empty object - no class was needed !!
var superhero = {};
superhero.name = 'Superman';
superhero.strength = 100;
Javascript对象就像相关属性的Java HashMap,其中键仅是字符串。 以下是“等效” Java代码:
Map<String,Object> superhero = new HashMap<>();
superhero.put("name","Superman");
superhero.put("strength", 100);
这意味着Javascript对象只是键/值对的多级“哈希映射”,不需要类定义。
功能就是价值
Javascript中的函数只是Function
类型的值,就这么简单! 举个例子:
var flyFunction = function() {
console.log('Flying like a bird!');
};
superhero.fly = flyFunction;
这将创建一个函数( Function
类型的值)并将其分配给变量flyFunction
。 然后在超级英雄对象中创建一个名为fly
的新属性,可以像这样调用该属性:
// prints 'Flying like a bird!' to the console
superhero.fly();
Java 没有 JavaScript的等效Function
类型,但差不多。 以具有Power
函数的SuperHero
类为例:
public interface Power {
void use();
}
public class SuperHero {
private Power flyPower;
public void setFly(Power flyPower) {
this.flyPower = flyPower;
}
public void fly() {
flyPower.use();
}
}
这是如何在Java 7和8中向SuperHero
传递函数:
// Java 7 equivalent
Power flyFunction = new Power() {
@Override
public void use() {
System.out.println("Flying like a bird ...");
}
};
// Java 8 equivalent
superman.setFly(
()->System.out.println("Flying like a bird ..."));
superman.fly();
因此,尽管Java 8中不存在Function
类型,但这最终不会阻止'类似于Javascript'的函数式编程风格。
但是,如果我们传递函数, this
关键字的含义会怎样?
“ this”关键字用法
什么JavaScript允许用做this
是相当令人惊讶相比,Java世界。 让我们从一个例子开始:
var superman = {
heroName: 'Superman',
sayHello: function() {
console.log("Hello, I'm " + this.heroName );
}
};
superman.sayHello();
该程序创建具有两个属性的对象superman
:字符串heroName
和名为sayHello
的Function
。 按预期运行该程序输出, Hello, I'm Superman
。
如果我们传递函数该怎么办?
通过传递sayHello
,我们可以轻松地在没有heroName
属性的上下文中结束:
var failThis = superman.sayHello;
failThis();
运行此代码片段将作为输出: Hello, I'm undefined
。
为什么
这是因为变量hello
属于全局范围,该范围不包含名为heroName
成员变量。 要解决这个问题:
在Javascript中,
this
关键字的值可以完全重写为我们想要的任何值!
// overrides 'this' with superman
hello.call(superman);
上面的代码片段将再次打印Hello, I'm Superman
。 这意味着,值this
取决于双方在该函数被调用的背景下,并在函数是如何被调用。
经典与原型继承
在Javascript中,没有类继承,而是对象可以直接从其他对象继承。 它的工作方式是每个对象都有一个指向“父”对象的隐式属性。
该属性称为__proto__
,父对象称为对象的原型 ,因此命名为Prototypal Inheritance。
查找属性时,JavaScript会尝试在对象本身中查找属性。 如果找不到,则尝试使用其原型,依此类推。 例如:
var avengersHero = {
editor: 'Marvel'
};
var ironMan = {};
ironMan.__proto__ = avengersHero;
console.log('Iron Man is copyrighted by ' + ironMan.editor);
该摘录将输出《 Iron Man is copyrighted by Marvel
。
如我们所见,尽管ironMan
对象为空,但它的原型确实包含属性editor
,该属性editor
可以找到。
这与Java继承相比如何?
现在,让我们来看看DC Comics购买复仇者联盟的权利:
avengersHero.editor = 'DC Comics';
如果再次调用ironMan.editor
,我们现在得到Iron Man is copyrighted by DC Comics
。 现在,具有avengersHero
原型的所有现有对象实例都可以看到DC Comics
而无需重新创建。
这种机制非常简单而且非常强大。 类继承可以完成的任何事情都可以通过原型继承来完成。 但是构造函数呢?
构造函数与构造函数
在Javascript中,尝试使对象创建类似于Java之类的语言。 让我们举个例子:
function SuperHero(name, strength) {
this.name = name;
this.strength = strength;
}
注意大写的名称,表明它是一个构造函数。 让我们看看如何使用它:
var superman = new SuperHero('Superman', 100);
console.log('Hello, my name is ' + superman.name);
此代码段输出Hello, my name is Superman
。
您可能会认为这看起来就像Java,这就是重点! 这是什么new
的语法确实是它创建了一个新的空对象,然后通过强制调用构造函数this
是新创建的对象。
为什么不建议使用这种语法呢?
假设我们要指定所有超级英雄都有一个sayHello
方法。 这可以通过将sayHello
函数放在一个公共原型对象中来完成:
function SuperHero(name, strength) {
this.name = name;
this.strength = strength;
}
SuperHero.prototype.sayHello = function() {
console.log('Hello, my name is ' + this.name);
}
var superman = new SuperHero('Superman', 100);
superman.sayHello();
这将输出Hello, my name is Superman
。
但是语法SuperHero.prototype.sayHello
看起来像Java一样! new
运算符机制看起来像Java,但同时却完全不同。
是否有一个推荐的替代
推荐的方法是完全忽略Javascript new
运算符,并使用Object.create
:
var superHeroPrototype = {
sayHello: function() {
console.log('Hello, my name is ' + this.name);
}
};
var superman = Object.create(superHeroPrototype);
superman.name = 'Superman';
与new
运算符不同,Java语言在Closures中绝对正确。
闭包vs Lambdas
Javascript闭包与以某种方式使用的Java匿名内部类没有什么不同。 以FlyingHero
类为例:
public interface FlyCommand {
public void fly();
}
public class FlyingHero {
private String name;
public FlyingHero(String name) {
this.name = name;
}
public void fly(FlyCommand flyCommand) {
flyCommand.fly();
}
}
我们可以在Java 8中向它传递一个fly命令,如下所示:
String destination = "Mars";
superMan.fly(() -> System.out.println("Flying to " +
destination ));
此代码段的输出是“ Flying to Mars
。 请注意, FlyCommand
lambda必须“记住”变量destination
,因为在以后执行fly
方法时需要它。
该函数可以记住其块范围之外的变量以供以后使用的功能的概念称为Java语言中的Closure 。 有关更多详细信息,请参阅此博客文章真正了解Java语言的闭包 。
Lambda和Closure之间的主要区别是什么?
在Javascript中,闭包如下所示:
var destination = 'Mars';
var fly = function() {
console.log('Fly to ' + destination);
}
fly();
Java闭包与Java Lambda不同,它没有约束,即destination
变量必须是不可变的(或者从Java 8开始实际上是不可变的)。
这种看似无害的差异实际上是Javascript闭包的“杀手级”功能,因为它允许它们用于创建封装的模块。
模块和封装
Javascript中没有类,也没有public
/ private
修饰符,但请再次查看以下内容:
function createHero(heroName) {
var name = heroName;
return {
fly: function(destination) {
console.log(name + ' flying to ' + destination);
}
};
}
此处定义了一个函数createHero
,该函数返回一个具有fly
函数的对象。 fly
函数在需要时“记住” name
。
封闭与封装有何关系?
当createHero
函数返回时,除了通过fly
之外,没有其他人可以直接访问name
。 让我们尝试一下:
var superman = createHero('SuperMan');
superman.fly('The Moon');
该片段的输出是《 SuperMan flying to The Moon
。 但是如果我们尝试直接访问name
会发生什么?
console.log('Hero name = ' + superman.name);
结果是Hero name = undefined
。 函数createHero
据说是一个Javascript封装的模块 ,具有封闭的“私有”成员变量和一个“公共”接口,作为带有函数的对象返回。
范围和吊装
了解Javascript中的块作用域很简单:没有块作用域! 看一下这个例子:
function counterLoop() {
console.log('counter before declaration = ' + i);
for (var i = 0; i < 3 ; i++) {
console.log('counter = ' + i);
}
console.log('counter after loop = ' + i);
}
counterLoop();
通过查看来自Java的内容,您可能会期望:
- 第3行出现错误:“变量i不存在”
- 值0、1、2被打印
- 第9行出现错误:“变量i不存在”
事实证明,这三件事中只有一件事是对的,输出实际上是这样的:
counter before declaration = undefined
counter = 0
counter = 1
counter = 2
counter after loop = 3
因为没有块作用域,所以循环变量i对于整个函数都是可见的。 这表示:
- 第3行看到已声明但未初始化的变量
- 循环终止后,第9行看到i
最令人困惑的是,第3行实际上看到了已声明但未定义的变量,而不是未定义i is not defined
。
这是因为Javascript解释器首先扫描函数以获取变量列表,然后返回以逐一解释函数代码行。
最终结果是它就像我被提升到顶部的变量一样,而这正是Javascript运行时真正看到的:
function counterLoop() {
var i; // i is 'seen' as if declared here!
console.log('counter before declaration = ' + i);
for (i = 0; i < 3 ; i++) {
console.log('counter = ' + i);
}
console.log('counter after loop: ' + i);
}
为避免因提升和缺少块作用域而引起的意外,建议始终在函数顶部声明变量。
这使吊装变得显眼且对开发人员可见,并有助于避免错误。 Javascript的下一版本(Ecmascript 6)将包含一个新的关键字“ let”以允许块作用域 。
结论
Javascript语言与Java有很多相似之处,但也有一些巨大差异。 继承和构造函数之类的一些差异很重要,但比日常编程要少得多。
库开发人员主要需要其中一些功能,而日常应用程序编程则不一定。 这与每天需要的某些Java对应程序不同。
因此,如果您不愿意尝试一下,请不要让其中某些功能阻止您进一步使用该语言。
可以肯定的是,在进行Java前端开发时,至少有些 Javascript或多或少是不可避免的,因此值得尝试一下。
翻译自: https://www.javacodegeeks.com/2014/06/javascript-for-java-developers.html