逆向JavaScript经验分享

Posted on

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

查看请求

前端无论怎么样的操作,本质上都是向服务器发送请求,然后解析请求数据,展现在页面上。所以说逆向的切入点就是查看网络请求!因为我要分析视频的源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,通过这个API,我们可以拦截请求并进行替换。我就使用这个API拦截了app_movie.js,注入了我带调试信息的JS。你就可以随意的调试了。关于这个API的使用DEMO,可能后续会放在我的Github上。

写在最后

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

tagged: JavaScript

模块化JavaScript开发

Posted on

前端开发的节奏真是日新月异。。。短短几年竟然发展如此迅速,JavaScript的各种框架库可以说比其他语言的总和还多。Google家的AngularJS,和Facebook的React各领风骚。自己写的前端项目也越来越复杂, 单纯的面向过程开发和单文件已经不能满足现有的需求。虽然ES6早就引入模块化特性,但是目前的浏览器还没有实现。还需要借助其它前端工具进行编译。。。下面我就顺便介绍下Web开发的历史。。。

Server Rendered

以前开发Web项目,全部是服务器来渲染页面,从数据库读入数据,浏览器只负责解释页面,这种情况下不考虑交互性,其实完全不用写任何Js代码。但是缺点也很大,处理全部交给服务器,服务器压力大。而且每次都是刷新加载,所以用户体验也不是很好。

Server-Rendered + AJAX

后来使用AJAX无刷新技术,对于简单的登录交互我们没有必要刷新页面,而是直接使用Js发送认证数据进行交互和登录。一定程度上减少了服务器的压力。

Single Page App

后来出现了谷歌的AngularJS这样的富客户端Js框架,做到了真正的页面和数据分离。全部使用JavaScript进行页面的渲染和交互,只向服务器请求必要的数据,并使用JSON的数据格式来传输。这样不仅有利于减少服务器压力,而且还可以复用到各种平台。但是JavaScript代码变得越来越多和复杂,必须考虑合适的架构和模块化开发才有利于之后的维护。

Webpack模块化工具

webpack模块化工具可以把你写的符合模块化规范的代码进行打包和整合到一个单子文件,这样就可以模块化自己的项目。但是官方文档实在是很难学习,自己琢磨了一段时间才算稍微能上手。并且还遇到点小问题,在此记录下,我们可以使用命令$ npm install webpack -g 安装webpack.但是我们使用的时候经常还是遇到找不到webpack的问题,是因为缺少环境变量NODE_PATH 可以通过设置export NODE_PATH="/usr/lib/node_modules来解决。

相关学习资料

webpack指南

webpack-howto

tagged: JavaScript

关于JavaScript跨域请求的相关知识

Posted on

2016年已经过了3个月了,还没来得及更新博客...实在是惭愧惭愧...最近确实也很忙,一边要打工,一边要写论文,一边还要找工作...

最近百度的导出插件出了点小问题,导致很多人无法正常和Aria2c通讯.经过仔细的分析,发现问题出在跨域请求上,接下来就要详细说说JavaScript的跨域请求.

JavaScript跨域请求

跨域请求指得是发起请求的资源所在域不同于该请求所指向资源所在的域的 HTTP 请求.最常见的就是跨域载入图片,我们可以看到很多网站主站和网站所使用的图片 是不同的域名的.这样做的好处是请求图片的时候不会发送主站的Cookies,因为不同域名嘛.而且还可以减少主站的服务器压力.但载入图片使用的是GET方法,比较简单.

跨域POST请求

我们经常使用POST请求发送各种指令数据,因为POST发送的数据没有长度限制.我们还可以在发送数据之前修改请求头,发送各种自定义的 HTTP Request Headers.但是 在跨域请求的时候POST的请求头被严格限制,被允许设置的请求头只有:

    - Accept
    - Accept-Language
    - Content-Language
    - Content-Type

并且允许的 Content-Type 只有一下三种:

    - application/x-www-form-urlencoded
    - multipart/form-data
    - text/plain

Preflighted requests

当我们发送的请求不满足上面的条件时,就必须发送预请求给目的站点,来查明这个跨站请求对于目的站点是不是安全可接受的。以下条件会触发预请求的发送:

    - 请求以 GET, HEAD 或者 POST 以外的方法发起请求。或者,使用 POST,但请求数据为 application/x-www-form-urlencoded, multipart/form-data 或者 text/plain 以外的数据类型。比如说,用 POST 发送数据类型为 application/xml 或者 text/xml 的 XML 数据的请求。
    - 使用自定义请求头(例如 Aria2c的验证使用的 Authorization)

结论

本次BUG的出现,是由于代码验证不规范,对于不需要用户名和密码验证的RPC地址.仍然发送自定义的 Authorization 验证头.导致触发OPTIONS请求,但是本身不支持验证的Aria2c客户端无法识别OPTIONS请求,只能返回500错误.导致通讯失败,至此 BUG分析完毕.

参考:

HTTP access control (CORS)

HTTP/1.1: Method Definitions

tagged: JavaScript

突破同源策略限制的方法

Posted on

同源策略基本介绍

同源策略(Same Origin Policy)是一种约定,它是浏览器最核心也是最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能会受到影响。可以说Web是构建在同源策略的基础之上的,浏览器只是针对同源策略的一种实现。

浏览器的同源策略,限制了来自不同源的“document”或脚本,对当前“document”读取或设置某些属性。

但是随着互联网的发展跨域的需求也越来越迫切,有些时候我们需要突破同源策略。下面开始介绍突破同源策略的几种方法:

使用HTTP头

因为JavaScript无法控制返回的HTTP头,所以可以在服务器端这个HTTP Header即可:

    self.set_header("Access-Control-Allow-Origin", "*")

使用JSONP

Jsonp(JSON with Padding)是 json 的一种“使用模式”,可以让网页从别的网域获取资料。jsonp是采用的js的回调机制来实现的。 本质上是因为GET方法不受同源策略限制,然后获取到的数据不是json,而是一段JavaScript并解释执行。

使用iframe

上面两种都是比较常见和常用的突破浏览器的方法,这种不常见的才是重点。虽然使用iframe算不上优雅,但也算开了眼界。在开发115网盘的时候,发现115 的主站网页和获取数据的网址是不同源的,也顺便研究了一下工作原理。
主站是:http://115.com/?mode=wangpan,
但是获取数据的API是:http://web.api.115.com/files/download?pickcode=?
这就造成了非同源,但是115巧妙的使用了一个桥接网页完成了跨域操作!
桥接网址:http://web.api.115.com/bridge_2.0.html?namespace=DownBridge&api=jQuery
关键代码:

    if(parent){
        var execObj = parent.window;
        if(params["namespace"]){
            var tmp = params["namespace"].split("."),f;
            while(f = tmp.shift()){
                execObj = execObj[f];
            }
        }
        var method = params["api"]? params["api"] : "DataAPI";
        execObj[method] = $;
    }

可以看到,桥接是首先判断父Window是否存在,获取namespace的值设置为 execObj 最后再获取api的值然后设置为 execObj的一个方法,不过这个方法对应的值肯定是 $,也就是整个JQuery,这样我们就可以使用页面 web.api.115.com 的JQuery发起数据请求从而绕过跨越的限制。

下面是115.com 上面注入的JS实现方法:

    set_down_url:function(){
        var self=this;
        DownBridge={};
          $('<iframe>').attr('src', 'http://web.api.115.com/bridge_2.0.html?namespace=DownBridge&api=jQuery').css({
            width: 0,
            height: 0,
            border: 0,
            padding: 0,
            margin: 0,
            position: 'absolute',
            top: '-99999px'
          }).one('load',function(){
            window.DownBridge.getFileUrl=function(pickcode,callback){
            this.jQuery.get('http://web.api.115.com/files/download?pickcode=' + pickcode, function (data) {
                     callback(data);
                    }, 'json');                        
            };
            window.DownBridge.getFileList=function(cate_id,callback){
            this.jQuery.get('http://web.api.115.com/files?aid=1&limit=1000&show_dir=1&cid=' + cate_id, function (data) {
                     callback(data);
                    }, 'json');                        
            };
          }).appendTo('html');
    }

115网站的这种实现方法很另类,不知道是否是有什么特殊需求导致的,但是很有意思~ 不过我还是不建议在网页里嵌入这么多的iframe,严重影响用户体验,非常不推荐!

tagged: JavaScript

JavaScript处理二进制数据

Posted on

最近在开发扩展的时候遇到需要获取MP3数据并且直接交给其它接口处理的问题,但是使用JQuery的AJAX进行 获取数据的时候已经把数据进行了编码,毕竟默认都是处理文本数据.
但是我需要的是二进制数据并且进行Base64编码方便进行传输,因为Base64编码的值全是可打印字符. Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据.

二进制数据转换成Base64编码

于是便找到了一个非常有用的MDN的文档Sending and Receiving Binary Data
不过稍作修改:

    var oReq = new XMLHttpRequest();
    oReq.open("GET", request.data, true);
    oReq.responseType = "blob";
    oReq.onload = function (oEvent) {
        var blob = oReq.response;
        var reader = new FileReader();
        reader.onload = function(readerEvt) {
            var binaryString = readerEvt.target.result;
            // console.log(binaryString);
            var testdata=btoa(binaryString);
            var data={
                "method":"getAudio",
                "data":'data:audio/mp3;base64,'+btoa(binaryString)
            };
            port.postMessage(data);
        };
        reader.readAsBinaryString(blob);
    };

    oReq.send(null);                                          
}

设置responseType为Blob 然后使用FileReader作为二进制数据读取,再使用btoa转码成Base64进行传输.

还有一种是读取需要上传的文件进行处理的方式和通过AJAX获取的方式差不多,代码在这里.使用Base64编码的好处是在需要二进制数据的地方,例如图片和音频,我们可以使用Base64编码进行替换获取这些资源的URL 可以减少HTTP请求,也可以做转发,我之所以有这个处理二进制数据的需求就是因为开发的一个插件无法在HTTPS页面上获取HTTP资源, 便通过 background js 进行获取数据然后Base64编码传输给content js进行使用.

Base64数据转换为ArrayBuffer

有时候需要把传入的二进制数据转换成ArrayBuffer进行处理就需要转换了

    var data = atob(base64String);
    var dataView = new Uint8Array(data.length);
    for (var i=0; i<data.length; ++i) {
      dataView[i] = data.charCodeAt(i);
    }
    var arrayBuffer=dataView.buffer;

今天暂时就写这么多吧,期末考试看书好累,头脑都不太清醒了. QAQ

tagged: JavaScript

JavaScript中一个经典的正则例子

Posted on

学习知识的最好方法就是看例子.(个人观点) 因为书上总是会把概念和公式讲的晦涩难懂,才显得作者高大上.所以我总是喜欢 通过例子学习知识.每次写正则的时候总是喜欢查看例子学习如何使用,因为正则实在太耗脑细胞了,记不住,也没必要记住.学会 如何使用并在项目中熟练运用就好了,经常使用的话也就自然记住了.

JavaScript语言精粹上面有个非常棒的学习例子,每次写正则的时候都会想起它.所以还是记一下分享给大家也方面我查询啦~

    var parse_url=/^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
    var url="http://www.ora.com:80/goodparts?q#fragment";
    var result=parse_url.exec(url);
    var names=['url','scheme','slash','host','port','path','query','hash'];
    var blanks='     ';
    var i;
    for(i=0;i<names.length;i+=1){
        console.log(names[i]+ ':' +blanks.substring(names[i].length),result[i]);
    }

让我们分解parse_url的各个部分,看看它是如何工作的:

^

^字符表示此字符串的开始.它是一个锚,指引exec不要跳过那些不像URL(non-URL-like)的前缀,只匹配那些从开头就像URL一样的字符串:

(?:([A-Za-z]+):)?

这个因子匹配一个协议名,但仅当它后面跟随一个:(冒号)的时候才匹配.(?:...)表示一个非捕获型分组(noncapturing group). 后缀 ? 表示这个分组是可选的. 问号表示重复0次或一次.(...)表示一个捕获型分组(capturing group).一个捕获型分组会复制它 所匹配的文本,并把其放到result数组里.每个捕获型分组都会被指定一个编号.第一个捕获型分组的编号是1,所以该分组所匹配的文本副本 会出现在result[1]中.[...]表示一个字符类,A-Za-z这个字符类包含26个大写字母和26个小写字母.连字符( - )表示范围从 A 到 Z. 后缀 + 表示这个字符类会被匹配一次或者多次.这个组后面跟着字符:,它会按字面进行匹配:

(\/{0,3})

下一个因子是捕获型分组2.\/表示该匹配/(斜扛).它是\(反斜杠)来进行转义,这样它就不会被错误地解释为这个正则表达式的结束符.后缀{0,3}表示 / 会被匹配0次,或者或者1~3次:

([0-9.\-A-Za-z]+)

下一个因子是捕获型分组3.它会匹配一个主机名,由一个或多个数字、字母,以及 . 或 - 字符组成. - 会被转义为 \- 以防止与表示范围的连字符想混淆:

(?::(\d+))?

下一个可选的因子匹配端口号,它是由一个前置 : 加上一个或多个数字而组成的序列. \d 表示一个数字字符.一个或多个数字组成的数字串会被捕获型 分组4捕获:

(?:\/([^?#]*))?

接下来是另一个可选的分组.该分组以一个 / 开始.之后的字符类[^?#]以一个 ^ 开始,它表示这个类包含除了?和#之外的所有字符.*表示这个字符类 会被匹配0次或多次.

注意这里的处理是不严谨的.这个类匹配除?和#之外的所有字符,其中包括了行结束符、控制字符,以及其他大量不应在此被匹配的字符.大多数情况下,它会按照 我们的预期去做,但某些恶意文本可能会有渗透进来的风险.不严谨的正则表达式是一个常见的安全漏洞发源地.写不严谨的正则表达式比写严谨的 正则表达式要容易得多.

(?:\?([^#]*))?

接下来,还有一个以一个 ? 开始的可选分组.它包含捕获型分组6,这个分组包含0个或多个非#字符.

(?:#(.*))?

最后一个可选分组是以 # 开始的 .会匹配除行结束符以外的所有字符.

$

$表示这个字符串的结束.它保证在这个URL的尾部没有其他更多内容了.

JavaScript函数中的闭包

Posted on

JavaScript中的闭包通常被视作 JavaScript 的高级特性.但是我看了好几次文档也没有明白过来.也没有实际应用过.最近重新学习JavaScript 打算好好学习下.

什么是闭包

闭包用通俗的说法来理解就是子函数可以使用父函数中的局部变量,这种行为叫做闭包.这个变量不是在这个代码块内或者全局上下文中定义的,而是 在定义代码块的环境中定义的(局部变量).一个经典的例子就是:

    function a(){
        var i=0;
        function b(){
        alert(++i);
        }
        return b;
    }
    var c = a();
    c();

函数b嵌套在函数a内部,函数a返回函数b.这样在执行了 var c = a() 之后,变量c变指向了函数a.再指向c()就会弹窗显示i的值.这段代码就创建了一个 闭包.因为函数a外的变量c引用了函数a内的函数b.就是说,当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包.

闭包的用途

  • 可以读取函数内部的变量
  • 这些变量的值始终保持在内存中
  • 模拟私有变量

上面的例子中,i的值只有b函数可以读取.当你不断执行c()的时候,发现i值不断增大,说明i没有被内存回收.

使用闭包构造模块

模块是一个提供接口却隐藏状态与实现的函数或对象.通过使用函数产生模块,我们几乎可以完全摈弃全局变量的使用. 举例来说,假定我们想要给String增加一个deentityify方法.它的任务是寻找字符串中的HTML字符实体并把它们替换为对应的字符. 这就需要在一个对象中保存字符实体的名字和它们对应的字符.但我们该在哪里保存这个对象呢?我们可以把它放到一个全局变量中, 但全局变量是魔鬼.我们可以把它定义在该函数的内部,但是那会带来运行时的损耗,因为每次执行该函数的时候该字面量都会被求值一次. 理想的方式就是使用闭包实现.

    Function.prototype.method=function(name,func){
        this.prototype[name]=func;
        return this;
    };
    String.method('deentityify',function(){
        //字符实体表,它映射字符实体打名字到对应的字符.
        var entity={
            quot:'"',
            lt:'<',
            gt:'>'
        };
        return function(){
            //查找以'&'开头和';'结束打子字符串.如果找到就替换.
            return this.replace(/&([^&;]+);/g,
                function(a,b){
                    var r=entity[b];
                    return typeof r === 'string' ? r : a;
                }
                );
        };
    }());
    cosole.log('&lt;&quot;&gt;'.deentityify());

通过给Function.prototype增加一个method方法,我们下次给对象增加方法的时候就不必键入prototype这几个字符,省掉了麻烦. 请注意最后一行,我们用()运算法立刻调用我们刚刚构造出来的函数.这个调用所创建并返回的函数才是deentityify方法.
关于这个的用法请参见wiki:IIFE

闭包的缺点

闭包会使局部变量始终存在,内存消耗很大.在IE浏览器中它会很容易导致泄露内存. 还有就是不要在循环中引用闭包.

参考资料

JavaScript函数调用的四种方式

Posted on

JavaScript函数调用除了声明时定义的形式参数,每个函数还接收两个附加的参数:this和arguments.参数this在面向的对象 编程非常重要,它的值取决于调用的模式.典型的两种函数定义方式有:

    var add=function(a,b){
        return a+b;
    };
    function add(a,b){
        return a+b; 
    };

这两种函数声明的区别是什么,是我上次百度面试的一个问题.当时没回答出来,很是尴尬.现在问了前端菊苣之后得出的答案是: 一个是声明了一个变量add,并给它赋初始值(一个匿名函数)另一个是声明了一个名为add的具名函数区别有:
1. add.name不同,trace的时候显示也不同(第一个add.name是"",第二个是"add")
2. 由于声明会被提升,但赋值语句不会,所以在语句之前访问add的时候结果不同(这点老IE还有点区别)

在JavaScript中四种调用模式分别是:方法调用模式,函数调用模式,构造器调用模式和apply调用模式.这些模式在如何初始化关键 参数this上存在差异.

方法调用模式

当一个函数被保存为对象的一个属性时,我们称它为一个方法.当一个方法被调用时,this被绑定到该对象.如果调用表达式包含一个提取属性 的动作(即包含一个 . 点表达式或[subscript]下标表达式),那么它就是被当做一个方法来调用.

    var myObject = {
        value:0,
        increment:function(inc){
            this.value+= typeof inc === 'number' ? inc : 1;
            console.log(inc);
        }
    };
    myObject.increment();  // 1
    myObject.increment(2); // 3

方法可以使用this访问自己所属的对象,所以它能从对象中取值或对对象进行修改.this到对象的绑定发生在调用的时候.这个"超级"延迟绑定(very late binding) 使得函数可以对this高度复用.通过this可取得他们所属对象的上下文的方法成为公共方法(public method).

函数调用模式

当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的:

    var sum = add(3,4);

以此模式调用函数时,this被绑定到全局对象(浏览器中就是window对象).这是语言设计上的一个错误.倘若语言设计正确,那么当内部函数被调用时,this应该仍然绑定到 外部函数的this变量.这个设计错误的后果就是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享该方法对对象的访问权.幸运的是, 有一个很容易的解决方案:如果该方法定义一个变量并给它赋值为this,那么内部函数就可以通过那个变量访问到this.按照约定,我们把那个变量命名为that:

    myObject.double=function(){ //给myObject增加一个 double 方法
        var that=this;
        var helper=function(){
            that.value=add(that.value,that.value);
            console.log(this);
        };
        helper();  //以函数的形式调用helper
    };
    myObject.double(); //以方法的形式调用double

构造器调用模式

如果在一个函数前面带上 new 来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时this会被绑定到那个新对象上.

    //创建一个名为Quo的构造器函数,它构造一个带有status属性的对象.
    var Quo = function(string){
        this.status=string;
    };
    //给 Quo的所以实例提供一个名为 get_status 的公共方法.
    Quo.prototype.get_status = function() {
        return this.status;
    };
    //构造一个 Quo 实例
    var myQuo = new Quo("confused");

一个函数,如果创建的目的就是希望结合 new 前缀来调用,那它就被称为构造器函数.这种构造器函数不推荐使用,详细内容请看《JavaScript语言精粹》后续内容.

Apply调用模式

因为JavaScript是一门函数式的面向对象编程语言,所以函数可以拥有方法.

apply方法让我们构建一个参数数组传递给调用函数.它允许我们选择this的值.apply方法接收两个参数,第一个是要绑定给this的值,第二个就是一个参数数组.

    var array=[3,4];
    var sum=add.apply(null,array);

    var statusObject={
        status:'A-OK'
    };
    //statusObject并没有继承自 Quo.prototype,但我们可以在 statusObject 上调用
    //get_status 方法,尽管 statusObject 并没有一个名为 get_status 的方法.
    var status=Quo.prototype.get_status.apply(statusObject);

以上内容摘录于《JavaScript语言精粹》.