你所不知道的 JavaScript II

初识 JavaScript

JavaScript 出生于 1995 年,目前它服务在 92% 左右的网站上面,GitHub 开源社区中 20% 左右的项目采用了 JavaScript。在互联网兴起的年代 JavaScript 发展迅猛,以 IE 为首的微软公司开始制定起自己的 JavaScript 标准,包括火狐、Opera 等,这时候标准泛滥,开发人员为了兼容各个版本浏览器每天加班加点,代码也越来越难以维护。后来 ECMA(欧洲计算机制造联合会,一个统一电脑操作格式标准、程序语言和输入输出的组织)创建了 ECMAScript(ES)标准,现在越来越多的浏览器开始遵从这一标准,此时的 WEB 前端语言才开始真正的统一起来,现在浏览器大部分都兼容 ES5(第五个版本)标准,ES6 在 2015 年已经发布,现在很多工程师已经开始使用新的标准开发,相信 JavaScript 未来会越来越完善越来越流行。

JavaScript 这种弱类型的语言,入门很简单,但是想玩转它需要下一些功夫,因为它和常规语言的语法有很多不同之处,通常不安常理出牌,如果不摸清楚它的性格,可能在出现 bug 的时候会困扰你半天。我从众多 JavaScript 反常规思维的语法中挑选了一些例子,知道了这些后,会帮助你更好的解决实践过程中的问题。

JavaScript 和 Java 的区别

很多人会将两者混为一谈,其实它们没有血缘关系!

  • 出生不同:JavaScript 是 Netscape 公司所生;Java 是 Sun 公司所生;
  • 编译不同:JavaScript 是“动态”或“解释执行”语言;Java 是编译型语言;
  • 类型不同:JavaScript 是弱类型的;Java 是强类型的;
  • 对象不同:JavaScript 的面相对象是基于原型的;Java 是基于类的;
javascript-vs-java.png

JavaScript 和 Java 的关系好比仓鼠和火腿的关系

混沌中的全局变量和局部变量

写给非工程师的备注:变量的声明好比在医院挂号,声明不同类型的变量,就好比你挂不同类型的号。那么作用域就好比医院,不可能你在民航医院挂的号能在朝阳医院也可以用

var a = 1;
b = 2;

function fn1() {
    var c = a;
    d = 4;
}

function fn2() {
    console.log(d);
}

fn1();
fn2();

运行这段代码,居然会打印出 4,后端 Java 工程师志兵吓一大跳,不可理喻,并且会对 JavaScript 语言的设计者破口大骂:“啥xx语言!”。常见的计算机语言中,变量是有一定的作用域的,在作用域内部的变量只会在这个作用域内部有效果,超出它的作用域后就无法使用了。

但是奇葩的 JavaScript 就是和别人不一样,d 这个变量仿佛穿越了时空,原来在 JavaScript 中,变量的声明有如下特征:

  • 不用 var 声明的变量(隐式声明)都会附属在全局变量 window 下面;
  • 所有变量的声明都会被提升到该变量作用域的最前面;
// begin: 变量的声明提升
var a;
window.b;
window.d;
// end: 变量的声明提升

a = 1;
window.b = 2;

function fn1() {
    // begin: 变量的声明提升
    var c;
    // end: 变量的声明提升

    c = 3;
    window.d = 4;
}

function fn2() {
    console.log(window.d);
}

fn1();
fn2();

课后作业:先有鸡还是先有蛋?

有了变量声明会被提升的知识后,看看下面两段代码,它们运行的结果如何?

a = 2;
var a;
console.log(a);
console.log(a);
var a = 2;

生死冤家 nullundefined

nullundefined 到底什么区别,经常搞得我们非常苦恼,判断一个变量存不存在我们该选谁?

  • null 已经声明,已经定义,只是没有内容:病了,已经到医院挂号了,只是还在等候医生就诊。
  • undefined 已经声明,但是未定义:病了,还没挂号,还在医院排队挂号。
  • nullobject 类型;
  • undefined 是 JavaScript 中的特殊类型 undefined
  • (null == undefined) => true 值相等;
  • (null === undefined) => false
var a = null;
var b;

console.log(a);
console.log(b);

console.log(typeof a);
console.log(typeof b);

console.log(a == b);
console.log(a === b);

原来,这与 JavaScript 的历史有关。1995 年 JavaScript 诞生时,最初像Java一样,只设置了 null 作为表示"无"的值。根据 C 语言的传统,null 被设计成可以自动转为 0。但是 JavaScript 的设计者 Brendan Eich,觉得这样做还不够,于是又设计了一个 undefined

课后作业:nullundefined0 的比较?

我们知道 (null == undefined) => true 它们两者值是相等的,那么:

console.log(null == 0);
console.log(undefined == 0);

1 + - + - - + + + - + 1 什么鬼?

看到如此神奇的运算,想必小伙伴们都惊呆了,是的!你看到的这一串在 JavaScrpit 中是可以运行起来的。运算还可以如此玩?

如何计算:

  • 一元运算的优先级要高于二元运算;
  • 一元加和一元减运算是从右向左的顺序来计算的:
1 + - + - - + + + - + 1
1 +(-(+(-(-(+(+(+(-(+(1))))))))))

总结规律:

  • 所有的 + 都忽略;
  • - 的奇偶:
    • 如果是奇数,那么等效于 1 - 1
    • 如果是偶数,那么等效于 1 + 1

琢磨不透的数字之间的计算

var a = 0.2 - 0.1; // => 0.1
var b = 0.3 - 0.2; // => 0.09999999999999998

怎么会这样!我喝醉了吗?刚刚上线完的长顺不敢相信自己的眼睛,抓耳挠腮,这可咋办?又要上一次线了!

这是由于精度造成的,JavaScript 的数字(无论是整数还是小数)采用的是64位双精度浮点类型,存储方式如下:

1位符号位+11位指数位+52位小数位

当在运算过程中超过了存储的位数,将会丢失因为丢失数据产生精度问题。

  • 先转换成二进制
  • 二进制计算
  • 转换成十进制
0.1 => 0.0001 1001 1001 1001…(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)

NaN What the xxxx is this?

NaN 性格非常纠结,它是数字类型,但是又代表不是一个数字(Not a Number),当在做计算时产生错误通常会得到 NaN 这个结果;这位哥们性格也非常孤僻,孤僻到和自己都合不来。

  • 它是一个数字类型;
  • Not a Number;
  • 它和任何数字进行比较运算的结果都是 false,甚至是自己;
console.log(typeof NaN); // => number
console.log(NaN == 1);   // => false
console.log(NaN > 1);    // => false
console.log(NaN < 1);    // => false
console.log(NaN == NaN); // => false
console.log(NaN > NaN);  // => false
console.log(NaN < NaN);  // => false

课后作业:NaN != NaN ?

1/00/0这也行?

在很多语言中 0 是不能被除的,甚至都无法通过编译,但是神奇的 JavaScript 不会报错,并且会给你一个满意的结果:

  • 1/0 => Infinity
    • Infinity 是 JavaScript 中的一个特殊的值,表示无穷大;
    • Infinity 是一个数字类型;
    • Infinity > 100000000 => true 但是 Infinity > NaN => flase(PS:NaN 就是屌!);
  • 0/0 => NaN

变幻莫测的 this

在很多语言中都有 this 这个关键词,它提供了一种优雅的方式来隐式传递一个对象引用,在很多面向对象的语言中(Java、C#),this 关键字的含义非常明确,就是代表当前对象,通常在编译期间就确定下来(编译期绑定);但是在 JavaScript 的世界中 this 是动态绑定的会让你摸不着头脑,经常会发现 this 并不是自己想要的这个

我们把 this 的指向分成以下几种情况:

外部函数调用

外部函数中调用 this 时,它将指向全局对象,浏览器中即为 window

function whatIsThis() {
    console.log(this);
}
whatIsThis();

会打印出 window 对象

内部函数调用

内部函数中调用this时,它和外部函数调用一样,指向了全局对象:

var obj = {
    a: 0,
    fn: function(){
        var whatIsThis = function(){
            console.log(this);
        }
        
        whatIsThis();
    }
};
obj.fn();

会打印出Window对象

对象的方法调用

对象的方法中调用this时,它将指向这个对象:

var obj = {
    a: 0,
    whatIsThis:() => {
        console.log(this);
    }
};
obj.whatIsThis();

会打印出当前Object对象

构造函数内部调用

构造函数的对象调用this时,它将指向这个对象:

function Class() {
    console.log(this);
}
var obj = new Class();

这里总结一下 this

this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用:用对象调用的方法中使用的 this 会指向该对象;直接调用的函数中使用的 this 会指向全局对象 window

课后作业:this 是否能给它赋上另外一个对象呢?

在 JavaScript 中很多对象都可以进行扩展,包括它自带的内置对象,那么 this 可以吗?

邪恶(evil)的 eval

eval 很强大,它可以将字符串当作 JavaScript 运行,也就是说它能让代码在代码中运行;当然控制不好对于程序来说也是一个灾难,它会把你的作用域弄的非常混乱,很多 JavaScript 的编辑器的插件不允许使用 eval 这个关键字。

var b = 2;

function foo(str, a) {
    eval(str);
    console.log(a,b);
}

foo('var b = 3;', 1); //=>1, 3
function foo(str, a) {
    var b = 3;
    console.log(a,b);
}

阅读上面的代码,我们将字符串输入到 foo 方法中,最后导致了意想不到的结果,输入的内容动态的变成了代码的一部分,使得变量 b 在函数中的作用域产生了变化,并且屏蔽了外部作用域中的同名变量。这样会导致程序原本的逻辑产生变化,它就好像一个不讲道理的小孩,从不按套路出牌,让我们的程序难以控制,我们不建议使用它。最后,在程序中动态生成代码的使用场景非常罕见,因为它所带来的好处无法抵消性能上的损失。

with or without

with 这个语法也是一个比较难掌握的,和 eval 一样它会让程序的作用域非常混乱。它本身的作用是:当重复引用同一个对象中的多个属性时,可以使用这个快捷方式不需要重复引用对象本身。

var obj = {
    a: 1,
    b: 2,
    c: 3
};

// 重复的使用 obj
obj.a = 2;
obj.b = 3;
obj.c = 4;

// 快捷的使用 obj
with(obj) {
    a = 3;
    b = 4;
    c = 5;
}

看起来这种方式的确很好用,我们不用重复的去调用某个对象而对其中的属性一个一个赋值了,让开发更佳简便了。但是,糟糕的事情总是会在 JavaScript 中发生:

function foo(obj) {
    with(obj) {
        a = 2;
    }
}

var o1 = {
    a: 3
};

var o2 = {
    b: 3
};

foo(o1);
console.log(o1.a); // 2

foo(o2);
console.log(o2.a); // undefined
console.log(a);    // 2 —— ops! a 被泄露到全局作用域上了

结论:with 不能使用在一个该对象不存在的属性上,否则会将该属性变成一个全局对象!这种改变作用域的事情是一件非常恐怖的事情,不建议在实际项目中使用它;

可计算属性名

我们知道很多面相对象的语言中,可以通过 . 获取到某个对象下的某个属性,例如:person.name。当然这一点在 JavaScript 中完全没有问题,更佳恐怖的是属性名可以是变量,还可以传入表达式,我们叫做可计算属性名:

var person = {
    name: 'jay'
};

var propertyName = 'name';

console.log(person.name);
console.log(person['name']);
console.log(person[propertyName]);
console.log(person['na' + 'me']); // ES6

火眼金星看清一切的 for/in

for/in 这个语句可以遍历对象的属性,它就好像 JavaC# 中的反射机制一样,能够通过它窥探某个类型的基本结构,我们可以采用层层递归的方式,看清楚某个对象的所有属性。

function foo(obj) {
    for(key in obj) {
        console.log(obj[key]);
    }
}

将对象打回 原型

原型在 JavaScript 中是一个非常重要也非常难理解的知识点,搞清楚它可能需要一个专门的话题来讲它,现在我们只在这里简单的讨论。

prototype__proto__ 的关系相当复杂,prototype 为构造函数原型,__proto__ 为对象的原型。所有构造函数的 __proto__ 都指向 Function.prototype;Function.prototype 的原型指向 Object.prototype,而 Object.prototype 的原型为空 null,这样就构成了一条复杂的原型链。

var klass = function() {
    this.name = "jay";
}
var obj = new klass();

console.log(obj.__proto__ === klass.prototype);
console.log(klass.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__ === null);

console.log(klass.constructor.__proto__ === Function.prototype);
console.log(Function.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__ === null);

//> Console:
//> true
//> true
//> true
//> true
//> true
//> true
javascript-prototype.png
Object.__proto__ === Function.__proto__
Function.__proto__ === Function.prototype

最后

工程师的工作非常枯燥,不过我们可以在枯燥的工作中找到一些有趣的事情。希望我们的工程师们能够善于发现工作中的乐趣,这样就会越来越充满激情。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容