C++程序员眼中丑陋的 JS

作为一个老程序员,长期工作在后端服务器的开发, 由于项目原因,最近会做一些前端 JavaScript 的开发,于是系统地学习了久违的 JavaScript, 第一感觉就是 Javascript 很丑陋。 当然,这是从一位老 C++ 程序员的角度来看 JavaScript , 所以觉的丑,而前端程序员可能看起来很美。

就象一开始看着总想吐,吐着吐着就习惯了,这里总结一下 JS 的槽点

1. 混乱的作用域

C++ 中有块级作用域,Javascript 中有变量提升和函数提升,其实都是提到作用域的最前面

C++的变量的作用域可能是全局的(文件级作用域),局部的(函数级或者块级作用域), 类(class)级别和名字空间(namespace)级别的。

JavaScript 呢,其实差不多,不过更加简陋,举例如下,相似的代码在 C++ 无疑报错

var color = "blue"; //global variable
//函数提升,这里可以直接调用 changeColor
changeColor();
//变量提升,background 声明已经提到最前面,只不过没有初始化 
console.log("color is " + color + ", background is " + background); //olor is red, background is undefined
//没有块级作用域, background 在全局作用域中
if(true) {
    var background = "brown";
}

console.log("background is " + background); //background is brown

var arr = [];
for(var i = 0; i < 10; i ++) {
    arr.push(i);
}
console.log(arr); //arr conains numbers of 0 ~ 9
console.log(i); //i is 10

function changeColor() { //global function
    var anotherColor = "red"; //local variable in changeColor

    function swapColors() {
        var tempColor = anotherColor; //local variable in swapColor
        if(color !== tempColor) {
            color = tempColor;
        }
    }
    swapColors();
}

2. 函数参数随便传

C++中的函数参数需要严格定义顺序,类型和个数,不一样的顺序,类型和个数的相同名字的函数就是不相同的函数,称为函数重载。

JavaScript 就不一样了,不存在函数重载,定义的函数有三个参数,你却可以传入三个参数,五个参数,任何个数都行,举例如下

function sortArgs(a, b, c) {
    return Array.from(arguments).sort(function (a, b) { return a - b; });
}

var retArr = sortArgs(5, 3, 7);
console.log(retArr);
//output: [ 3, 5, 7 ]

retArr = sortArgs(5, 3, 7, 1, 9, 8);
console.log(retArr);
//output: [ 1, 3, 5, 7, 8, 9 ]

3. 蹩脚的对象构建和继承

面向对象的基本概念之一就是类, 类定义了成员属性和方法,通过类就可创建多个具有相同属性和方法的对象。

JS 中没有类的概念,它只有对象- “对象是一个无序属性的集合,属性可以是一个基本值,一个对象,或者一个函数”。

C++ 中有构造函数,拷贝构造函数,析构函数, JS 中只有函数,new 后面跟任意一个函数,这个函数就是构造函数,而 每个函数都有一个原型对象,每个函数的原型都有一个 constructor 属性,这个属性就指向函数本身。有点绕。

定义和创建对象也挺简单,例如以下三种方法

function Book() {
    this.title = "";
    this.author = "";
    this.edition = 1;
    this.getAuthor = function() {
        return this.author;
    }
    this.setAuthor = function(author) {
        this.author = author;
    }
    this.toString = function() {
        return `title=${this.title}, author=${this.author}, edition=${this.edition}`;
    }
}

function createBook(title, author, edition) {
    var obj = new Object();
    obj.title = title;
    obj.author = author;
    obj.edition = edition;

    obj.getAuthor = function() {
        return this.author;
    }
    obj.setAuthor = function(author) {
        this.author = author;
    }
    obj.toString = function() {
        return `title=${this.title}, author=${this.author}, edition=${this.edition}`;
    }
    return obj;
}

var book0 = {
    title: "reacforing",
    author: "Martin",
    edition: 2,
    toString: function() {
        return `title=${this.title}, author=${this.author}, edition=${this.edition}`;
    }

}

console.log("book0:", book0.toString());

var book1 = new Book();
book1.title = "test driven development";
book1.setAuthor("Kent");
console.log("book1:", book1.toString());


var book2 = new Book();
book2.title = "feature driven development";
book2.setAuthor("Unknown");
console.log("book2:", book2.toString());

var book3 = createBook("metrics driven development", "Walter", 1);
console.log("book3:", book3.toString());

上面的三个对象 book0, book1, book2, book3 各自的属性和方法都是独立的,显然效率不高,起码成员方法是可以共享的。

我们可以用继承来做到成员的共享,JavaScript 没有类,只有对象,没有类继承,只有对象继承。

一个对象 A 以另外一个对象 B 为原型,那么就可以认为 A 继承自 B。
JS中专门用来作原型的对象就好比C++中的类,原型对象的继承就好比类继承。

例如上面的 Book 函数好比 Book 类, 我们要声明一个 EBook 函数从 Book 函数继承,也就是把 EBook 的原型对象设为 Book.
这样做还不够,还需要把 EBook.prototype 的 constructor 属性改回 EBook 函数, 代码如下:

var assert = require('assert');

var book0 = {
    title: "reacforing",
    author: "Martin",
    edition: 2,
    toString: function() {
        return `title=${this.title}, author=${this.author}, edition=${this.edition}`;
    }

}
console.log("book0:", book0.toString());

var book1 = new Book("test driven development","Kent", 4);
console.log("book1:", book1.toString());


var book2 = new Book("feature driven development","Unknown", 1);
console.log("book2:", book2.toString());

var book3 = createBook("metrics driven development", "Walter", 1);
console.log("book3:", book3.toString());


function createBook(title, author, edition) {
    var obj = new Object();
    obj.title = title;
    obj.author = author;
    obj.edition = edition;

    obj.getAuthor = function() {
        return this.author;
    }
    obj.setAuthor = function(author) {
        this.author = author;
    }
    obj.toString = function() {
        return `title=${this.title}, author=${this.author}, edition=${this.edition}`;
    }
    return obj;
}


function Book( title, author, edition) {
    this.title = title;
    this.author = author;
    this.edition = edition;

    this.getAuthor = function() {
        return this.author;
    }
    this.setAuthor = function(author) {
        this.author = author;
    }
    this.toString = function() {
        return `title=${this.title}, author=${this.author}, edition=${this.edition}`;
    }
}

function EBook( title, author, edition, url) {
    Book.call(this, title, author, edition);
    this.url = url;

    this.getUrl = function() {
        return this.url;
    }
    this.setUrl = function(url) {
        this.url = url;
    }

    this.toString = function() {
        return `title=${this.title}, author=${this.author}, edition=${this.edition}, url=${this.url}`;
    }
}

Book.prototype.getEdition= function() {
    return this.edition;
}

console.log("book1.constructor: ",book1.constructor)

EBook.prototype = new Book();
ebook1 = new EBook("effective c++", "mayer", 3, "http://t.c");
console.log("ebook1.constructor: ",ebook1.constructor);

Object.defineProperty(EBook.prototype, "constructor", {
    enumrable: false,
    value: EBook,
    writable: true
})
console.log("ebook1.constructor: ",ebook1.constructor);
console.log("ebook1.edition: ", ebook1.getEdition());
console.log("ebook1: ", ebook1.toString());


assert(book1 instanceof Book);
assert(ebook1 instanceof EBook);

C++ 中优雅的 "class EBook: publich Book" 在JavaScript 中搞得这么丑陋,好在 ES6中终于引入了 class 关键字,这样的定义看起来好多了

class User {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    isAlive() {
        return true;
    }

    static compare(user1, user2) {
        return user1.age - user2.age;
    }
}

var alice = new User("Alice", 20);
console.log("Alice: ", alice);

var bob = new User("Bob", 30);
console.log("Bob: ", bob);

assert(alice.isAlive());
assert(User.compare(alice, bob) < 0);

class Employee extends User {
    constructor(name, age, department) {
        super(name, age);
        this.department = department;
    }

    getDepartment() {
        return this.department;
    }
}

var carl = new Employee("carl", 40, "QA")
console.log("carl: ", carl);

其实,这只是语法糖,底层还是基于原型链的实现

4. 数组其实是对象

C++ 在语言层面就很好地支持了数组,而JavaScript 的数组其实就是一种对象,数组元素可以是任意类型,JS 数组是动态的,它没有边界,可以根据需要增长或缩减,它可以是稀疏的,数组元素的索引可以不连续.

const users = ["alice", "bob", "carl", "david"];
users.push("elain");
users.unshift("walter");
console.log("users: ", users);
users.pop();
users.shift();
console.log("users: ", users);

users[1000] = "finance";
console.log("users length: ", users.length);
console.log("--------- users ------------");
for(var i=0, len=users.length; i < len; i++) {
    if(users[i] === undefined) {
        continue;
    }
    console.log(i, "=>", users[i]);
}

console.log("--------- departments ------------");
//array contains any type
const departments = new Array("dev", "qa", "ops", 1.0, 2, []);

console.log(departments);
//no array boundary
console.log(users[10]); //print undefined

5. 运算符混乱

首先就是严格相等与非严格相等, 所以我们尽量使用 ===!== 来进行两个值的比较

var a = "10";
var b = 10
console.log(null == undefined); //true
console.log(a == b); //true
console.log(a === b); //false

逻辑运算符就更乱了,逻辑表达式返回的值并不一定是 true 或 false,而可能是任意类型

  • JS 中真假值的判断

    • true:. 对象、非零数字、非空字符串
    • false: 0、""(空字符串)、null、false、undefined、NaN
  • JS中的短路求值

    • a&&b:左操作数为假值时,返回左操作数,否则返回右操作数。
      -a||b:左操作数为假值时,返回右操作数,否则返回左操作数。
  • 通过!! 把一个其他类型的变量转成的bool类型

举例如下:

var a = "10";
var b = 10;
var c = null;
console.log(c == undefined); //true
console.log(a == b); //true
console.log(a === b); //false

console.log(typeof a);//string
console.log(typeof !!a, !!a)//boolean, true

console.log(c || 20);//20
console.log(c && 20);//null

6. 蹩脚的封装

面向对象最重要的特性可能就是封装了,C++ 中有 public, protected 和 private 三种可见性,而 JS 呢,全都没有,只能通过代理或闭包这种比较麻烦的方式来封装隐藏私有成员。

闭包是指有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式是在一个函数内部创建另一个函数。

function Task() {
    let priority;

    this.getPriority = () => priority;

    this.setPriority = value => { priority = value;};
}

const task = new Task();
task.setPriority(1);
console.log(task.getPriority());

7. 回调地狱

JS 的匿名函数回调往往会搞成下面这个样子,一层一层回调下去,超过三层就让人头大了

firstFunction(args, function() {
  secondFunction(args, function() {
    thirdFunction(args, function() {
      // And so on…
    });
  });
});

好在 ES6 有了 promise , 情况会好很多

参见 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

8. 全局变量泛滥

在 JS 中, 不在函数内部的变量都是全局的,全局变量泛滥会造成许多问题, 解决的方法是减少全局变量的使用,整个应用可以只用一个唯一的全局变量。

var MyApp = {}
MyApp.project = {
   "count":0,
   "tasks": {}
}

9. 易错的 with

使用 with 的效果可能不可预料,所以最好别用

with(obj) {
    a = b;
}

//它有可能是下面四种情况
a = b;
a = obj.b;
obj.a = b;;
obj.a = obj.b;

10. 邪恶的 eval

eval 看起来很好用,可是却很危险,搞不好就会变得 "evil" (邪恶的)

foo = 2;
eval('foo = foo + 2; console.log(foo);'); //print 4

邪恶的原因在于

  1. 性能原因: 由于 eval 会用到 JS 的解释/编译功能, 性能会差很多
  2. 注入的可能: 类似于 SQL 注入, eval 的内容也可被注入
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值