JavaScript中的数据类型

隔了一个月,我又来发博客了。最近的工作老是在写 CSS 和 HTML。但是我更想学习 JS 啊!我一直都觉得 HTML 和 CSS 是属于设计范畴的,而 JS 才是真正属于工程师的逻辑范畴。 况且最近 Github 上有一个神奇的项目Screenshot-to-code-in-Keras (opens new window)可以把截图直接生成 HTML 代码,我觉得只是单纯的从 PSD 翻译成页面的工作迟早要被淘汰。

最近在读 You Don't Know JS 这本书。书上讲解了很多关于 JS 的细节知识,对于深入了解 JS 有很大帮助。所以想在读的过程中把一些觉得有意思的东西记下来,便于以后复习。

# 类型

JavaScript 中有七种内置类型:

  1. 空值(null)
  2. 未定义(undefined)
  3. 布尔值(boolean)
  4. 数字(number)
  5. 字符串(string)
  6. 对象(object)
  7. 符号(symbol, ES6 新增)

除了对象之外,通称基本类型。

# JavaScript 中的设计 BUG

typeof null === 'object'; // true
1

正确的返回结果应该是 null。但这个 BUG 由来已久,修复反而会出问题。所以我们需要使用复合条件来判断:

!a && typeof a === 'object';
1

接下来是NaN的问题:

var a = 2 / 'foo';
var b = 'foo';
a; // NaN
b; // "foo"
window.isNaN(a); // true
window.isNaN(b); // true
NaN === NaN; // false
1
2
3
4
5
6
7

很显然"foo"不是 NaN,但显然它也不是数字。这个 BUG 也存在很久了,在 ES6 时代,我们可以使用 Number.isNaN 来解决。 ES6 之前的 polyfill 是:

if (!Number.isNaN) {
  Number.isNaN = function (n) {
    return typeof n === 'number' && window.isNaN(n);
  };
}
1
2
3
4
5

并且 NaN 是 JS 中唯一一个不等于自身的值。

# 值和类型

JavaScript 中的变量是没有类型的,只有值才有。变量可以随时持有任何类型的值。

undefined 和 undeclared. 变量在未持有的时候为 undefined, 此时 typeof 返回 undefined. 大多数开发者倾向于将 undefined 等同于 undeclared(未声明),但在 JavaScript 中它们完全是两回事。已在作用域中声明但还没有赋值的变量,是 undefined 的。相反,还没有在作用域中声明过的变量,是 undeclared 的。

var a;
a; // undefined
b; // ReferenceError: b is not defined
typeof a; // "undefined"
typeof b; // "undefined"
1
2
3
4
5

虽然 b 是一个 undeclared 变量,但 typeof b 并没有报错。这是因为 typeof 有一个特殊的安全防范机制。防止因为未定义导致程序终止运行。

# 值和引用

在许多编程语言中,赋值和参数传递可以通过值复制(value-copy)或者引用复制(reference-copy)来完成,这取决与我们使用什么语法。但是 JavaScript 对值和引用的赋值 / 传递在语法上没有区别,完全根据值的类型来决定。

简单值(即标量基本类型值,scalar primitive)总是通过值复制的方式来赋值/传递,包括 null 、 undefined 、字符串、数字、布尔和 ES6 中的 symbol 。

复合值(compound value)——对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值/传递。由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向。

var a = [1, 2, 3];
var b = a;
a; //[1,2,3]
b; // [1,2,3]

// 然后
b = [4, 5, 6];
a; // [1,2,3]
b; // [4,5,6]
1
2
3
4
5
6
7
8
9

b=[4,5,6] 并不影响 a 指向值 [1,2,3] ,除非 b 不是指向数组的引用,而是指向 a 的指针,但在 JavaScript 中不存在这种情况!

参考:

You Don't Know JS (opens new window)

JavaScript对象的常见操作

工作也算是稳定了,不过理想和现实的差距还是很大。程序员的职责是把枯燥的工作自动化,而不是去进行重复劳动。最近在写 JavaScript 程序的时候,遇到了很多对象相关的操作。所以写点东西来总结下这半个月的成长。

# JavaScript 对象复制

JavaScript 对象默认全部是拷贝引用,也就是所谓的浅拷贝。所以我们在对象操作的时候,要记住是否需要进行拷贝。一般我们使用对象的时候,都是需要对其某个属性进行修改。所以正确的写法是:

const bar = { a: 1, b: 2, c: {d: 4}}
const foo = {...bar, b: 3}
foo.c.d =10
console.log(bar.c.d)
// { a: 1, b: 2, c: {d: 10}}
1
2
3
4
5

这样就同时进行拷贝和修改。注意这里使用的是 JavaScript 的 es6 语法。如果要在浏览器运行,你需要 Babel (opens new window) 来进行转换。注意,如果对象里面还有对象的话,这种方式也仅仅是浅拷贝。深拷贝必须遍历所有属性进行复制,在效率上有很大问题,所以我们尽量不要去用深度拷贝来解决问题。 这里对象的复制是使用的Object.assign (opens new window) 针对对象的简单类型可以进行复制,但是对象还是引用。如果实际的应用场景确实需要进行深度拷贝,可以使用Lodash (opens new window)。提供了很多常用的类库。

# JavaScript 数组

针对数组,现在已经不推荐用 for 循环来进行处理了,请使用数组的map (opens new window), filter (opens new window)reduce (opens new window)来处理数组的操作。相信这三个方法足以满足需求。

# JavaScript 开发规范

现在 JavaScript 已经是 es6 的时代,所以我们也需要顺应时代学习新知识。这里我建议大家读读 airbnb 的JavaScript Style Guide (opens new window)。这里不仅教会你正确的编码格式,更多的是优秀的写法。如何合理的拷贝对象,遍历数组等等。

目前就写到这里,工作之后并没有多少成长。写博客都发呆了很久该写什么。。。

逆向JavaScript经验分享

最近心血来潮,想看个国漫。结果爱奇艺独播,还每集那么老长的广告,可是把我恶心的不要不要的。然后为了展现程序员的自我修养,我要分析下这个视频的源文件地址,直接观看,跳过广告!凭着我分析百度网盘的经验,花了三个晚上,总算搞定了。顺便分享下整个逆向分析过程,和大家学习。

# 查看请求

前端无论怎么样的操作,本质上都是向服务器发送请求,然后解析请求数据,展现在页面上。所以说逆向的切入点就是查看网络请求!因为我要分析视频的源 MP4 文件而不是 flash 文件,所以我把浏览器模拟成手机让爱奇艺载入手机版界面以便抓包获取 MP4 文件。切换成手机版很简单,只需要在 Chrome 上按 F12 打开开发人员工具,点击左上角的 Toggle device toolbar图标就可以切换成手机设备了,我选择模拟的设备是 Nexus 6P。这时候重新刷新页面,查看NetWork面板。里面会有这个页面载入发起的所有请求,我们重点找的是各种 JS 发起的请求和带参数的请求。最笨的办法就是一个请求一个请求看一遍,总有你需要的。

# 分析请求

通过一个个查找,我们找到了一个请求返回了一个 JSONP 格式的数据,这个数据里面包含了 MP4 源文件的地址。注意,很多人分析请求的时候经常喜欢找 MP4 文件的请求,这个可以算是一个切入点。但是一般网站不会把视频网址固定死的,所以找到 MP4 请求并没有什么实际意义,而是一定有一个请求去向服务器索取真实的 MP4 地址,一般这个数据都是 JSON 格式的。所以只要耐心找,一定找得到的。很多请求都只不过是资源文件,所以很容易过滤掉的。

接下来就请看我们找到的这个关键请求: Request 从这个请求的 Response 可以得知这是我们需要的请求,但这也只不过才是开始。我们知道了这个请求,就需要思考如何构造这个请求,来获取真实的 MP4 地址。一般请求的网址和路径都是固定的,所以我们的重点工作就是构造请求参数。 这个请求总共有 17 个请求参数,真是够多的。但是我们不知道所有请求参数是否是必须的。有可能服务器只校验其中的几个参数,所以我们没有必要对每个参数都分析一遍。

这时候就需要拷贝下来这些参数来进行分析,在界面的左侧面板,右键单击之后会出现 copy as cURL菜单。这样我们就可以在 Linux 的终端里调试这个请求,你可以先拷贝到记事本上,然后修改请求之后再终端里跑一遍看看能否返回正常的数据。有些服务器也会对Request Headers进行校验,所以可以试试去掉相关的headers例如 cookie,UA。最终调试出可以得到合法数据的最简单请求。爱奇艺这个例子对Request Headers没有进行任何校验,反而对请求参数很苛刻,少一个都不行,最后我们会解释为什么爱奇艺对请求参数检查这么苛刻。

# 逆向 JS 文件找到相关请求如何构造

从上面我们知道了,这个请求只对参数进行了校验,Request Headers并不需要。那重点就是逆向 JS 找到如何构造参数即可。

通过Sources面板可以看到这个页面加载的所有 JS,但是这个看起来也太多了吧。所以这个不是方法,重点就是查找关键的 JS 文件。Ctrl+Shift+F可以查找所有的文件,所以我们可以利用搜索大法来找到关键的 JS 文件。一般也可以通过Network查看请求的触发者,但是很多封装之后的 JS,是无法通过请求追溯到构造的 JS 文件的。但是无论这个 JS 文件怎么压缩混淆,网址是必须要有的。所以我们搜索cache.m.iqiyi.com/jp/tmts就找到了关键的 JS 文件了!找到 JS 文件之后,会跳转到Sources面板,点击右下角的{}Pretty print 会把压缩的 JS 自动进行换行和排版,方便分析。这时候通过分析这个 JS 文件,我们就知道如何构造这个请求了。

# 站在巨人的肩膀上

我们没有必要每个参数都自己用 JS 实现一遍,因为人家已经实现好了,我们只要调用就好。这个请求的关键是getTmtsVf函数。这个函数对请求进行了哈希,添加了一个 vf 参数,所以我对参数进行了任何的修改都会导致请求非法。无论怎么实现的 JS,有些参数只能通过全局变量来共享,所以对于那些参数,我们可以直接使用。例如爱奇艺有个全局的Q变量,通过这个变量可以获得很多需要的参数。

# 终极解决方案

对参数的构造完全取决于最后个人对 JS 文件的理解的功底,能看懂压缩的 JS 的逻辑才能成功提取需要的请求参数。但也有些变态的 JS 文件很难理解,不进行动态调试分析的话很难明白。这时候我们就不得不请出 Chrome 的大杀器:webRequest (opens new window),通过这个 API,我们可以拦截请求并进行替换。我就使用这个 API 拦截了app_movie.js,注入了我带调试信息的 JS。你就可以随意的调试了。关于这个 API 的使用 DEMO,可能后续会放在我的 Github 上。

# 写在最后

其实可以分析桌面版本的 flash 视频,然后使用 B 站的这个flv.js (opens new window)来解决播放问题。最终我写了一个油猴脚本来一键输出 MP4 文件。有兴趣的可以私信我。