javaScript高级程序设计----笔记第七到九章

函数表达式

  • 函数表达式的特征
  • 使用函数实现递归
  • 使用闭包定义私有变量

函数表达式特征

函数声明提升

执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。

sayHi();
function sayHi(){
    alert("Hi!");
}

匿名函数

var functionName = function(arg0, arg1, arg2){ 
    //函数体
};

这种形式看起来好像是常规的变量赋值语句,即创建一个函数并将它赋值给变量 functionName。 这种情况下创建的函数叫做匿名函数(anonymous function),因为 function 关键字后面没有标识符。(匿名函数有时候也叫拉姆达函数。)

注意:函数声明和函数表达式的区别,函数表达式在使用前必须先赋值

递归

递归函数是在一个函数通过名字调用自身的情况下构成的

function factorial(num){
    if(num <= 1) {
        return 1;
    }else { 
        return num * factorial(num-1);
    }
}

这是一个经典的递归阶乘函数。虽然这个函数表面看来没什么问题,但下面的代码却可能导致它出错

var anotherFactorial = factorial; 
factorial = null;
alert(anotherFactorial(4)); //出错!

以上代码先把 factorial()函数保存在变量 anotherFactorial 中,然后将 factorial 变量设 置为 null,结果指向原始函数的引用只剩下一个。但在接下来调用 anotherFactorial()时,由于必 须执行 factorial(),而 factorial 已经不再是函数,所以就会导致错误。在这种情况下,使用 arguments.callee 可以解决这个问题。

function factorial(num){
    if (num <= 1){
        return 1;
    } else {
        return num * arguments.callee(num-1);
    }
}

但在严格模式下,不能通过脚本访问 arguments.callee,访问这个属性会导致错误。不过,可以使用命名函数表达式来达成相同的结果

var factorial = (function f(num){
    if (num <= 1){
        return 1;
    } else {
        return num * f(num-1);
    } 
});

闭包

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

闭包与变量

作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最 后一个值。

function createFunctions(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(){
            return i; 
        };
    }
    return result;
}

这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返自己的索引值,即位置 0 的函数 返回 0,位置 1 的函数返回 1,以此类推。但实际上,每个函数都返回 10。因为每个函数的作用域链中 都保存着 createFunctions()函数的活动对象,所以它们引用的都是同一个变量 i。当createFunctions()函数返回后,变量 i 的值是 10,此时每个函数都引用着保存变量 i 的同一个变量 对象,所以在每个函数内部 i 的值都是 10。但是,我们可以通过创建另一个匿名函数强制让闭包的行为符合预期

function createFunctions(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(num){
            return function(){
                return num;
            }; 
        }(i);
    }
    return result;
}

在重写了前面的 createFunctions()函数后,每个函数就会返回各自不同的索引值了。在这个版本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋 给数组。这里的匿名函数有一个参数 num,也就是最终的函数要返回的值。在调用每个匿名函数时,我 们传入了变量 i。由于函数参数是按值传递的,所以就会将变量 i 的当前值复制给参数 num。而在这个 匿名函数内部,又创建并返回了一个访问 num 的闭包。这样一来,result 数组中的每个函数都有自己num 变量的一个副本,因此就可以返回各自不同的数值了

this对象

匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。但有时候 由于编写闭包的方式不同,这一点可能不会那么明显

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    } 
};
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

每个函数在被调用时都会自动取得两个特殊变量this 和 arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量,不过,把外部作用域中的 this 对象保存在一个闭包能够访问 到的变量里,就可以让闭包访问该对象了

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        var that = this;
        return function(){
            return this.name;
        };
    } 
};
alert(object.getNameFunc()());  //"My Object"

注意:this 和 arguments 也存在同样的问题。如果想访问作用域中的 arguments 对 象,必须将对该对象的引用保存到另一个闭包能够访问的变量中。

模仿块级作用域

function outputNumbers(count){
    for (var i=0; i < count; i++){
        alert(i); 
    }
    alert(i); //计数 
}

这个函数中定义了一个 for 循环,而变量 i 的初始值被设置为 0。在 Java、C++等语言中,变量 i只会在 for 循环的语句块中有定义,循环一旦结束,变量 i 就会被销毁。可是在 JavaScrip 中,变量 i是定义在 ouputNumbers()的活动对象中的,因此从它有定义开始,就可以在函数内部随处访问它。

模仿块级作用域语法:

(function(){ 
    //这里是块级作用域
})();

以上代码定义并立即调用了一个匿名函数。将函数声明包含在一对圆括号中,表示它实际上是一个 函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。

私有变量

严格来讲,JavaScript 中没有私有成员的概念;所有对象属性都是公有的。不过,倒是有一个私有 变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。 私有变量包括函数的参数、局部变量和在函数内部定义的其他函数

function add(num1, num2){
    var sum = num1 + num2;
    return sum;
}

有权访问私有变量和私有函数的公有方法称为特权方法(privileged method)。有两种在对象 上创建特权方法的方式。

function MyObject(){
    var privateVariable = 10;

    function privateFunction(){
        return false;
    }

    //特权方法
    this.publicMethod = function (){
        privateVariable++;
        return privateFunction();
    };
}

这个模式在构造函数内部定义了所有私有变量和函数。然后,又继续创建了能够访问这些私有成员 6的特权方法。能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的 所有变量和函数。对这个例子而言,变量 privateVariable 和函数 privateFunction()只能通过特 权方法 publicMethod()来访问。在创建 MyObject 的实例后,除了使用 publicMethod()这一个途 径外,没有任何办法可以直接访问 privateVariable 和 privateFunction()。

静态私有变量

通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法

(function(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //构造函数
    MyObject = function(){ };
    //公有/特权方法
    MyObject.prototype.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };
})();

这个模式创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法。在私有作用域中, 首先定义了私有变量和私有函数,然后又定义了构造函数及其公有方法。公有方法是在原型上定义的, 这一点体现了典型的原型模式。需要注意的是,这个模式在定义构造函数时并没有使用函数声明,而是 使用了函数表达式。函数声明只能创建局部函数,但那并不是我们想要的。出于同样的原因,我们也没 有在声明 MyObject 时使用 var 关键字。记住:初始化未经声明的变量,总是会创建一个全局变量。 因此,MyObject 就成了一个全局变量,能够在私有作用域之外被访问到。但也要知道,在严格模式下 给未经声明的变量赋值会导致错误。

模块模式

单例(singleton),指的就是只有一个实例的对象。 按照惯例,JavaScript 是以对象字面量的方式来创建单例对象的

var singleton = {
    name : value,
    method : function () { 
        //这里是方法的代码
    } 
};

模块模式通过为单例添加私有变量和特权方法能够使其得到增强

var singleton = function(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //特权/公有方法和属性
    return {
        publicProperty: true,
        publicMethod : function(){
            privateVariable++;
            return privateFunction();
        }
    }; 
}();

这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的

增强的模块模式

这种增强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况

var singleton = function(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //创建对象
    var object = new CustomType();
    //添加特权/公有属性和方法 object.publicProperty = true;
    object.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };
    //返回这个对象
    return object;
}();

BOM

  • 理解 window 对象——BOM 的核心
  • 控制窗口、框架和弹出窗口
  • 利用 location 对象中的页面信息
  • 使用 navigator 对象了解浏览器

window对象

全局作用域

window 对象同时扮演着 ECMAScript 中 Global 对象的角色,因此所有在全局作用域中声明的变量、函数都会变成 window 对象的属性和方法

var age = 29;
function(){
    alert(this.age);
}
alert(window.age); //29 
sayAge(); //29 
window.sayAge(); //29

抛开全局变量会成为window对象的属性不谈,定义全局变量与在window对象上直接定义属性还 是有一点差别:全局变量不能通过 delete 操作符删除,而直接在 window 对象上的定义的属性可以

var age = 29;
window.color = "red";

//在IE < 9 时抛出错误,在其他所有浏览器中都返回false 
delete window.age;

//在IE < 9 时抛出错误,在其他所有浏览器中都返回true 
delete window.color; //returns true

alert(window.age);   //29
alert(window.color); //undefined

窗口位置

用来确定和修改 window 对象位置的属性和方法有很多。IE、Safari、Opera 和 Chrome 都提供了screenLeft 和 screenTop 属性,分别用于表示窗口相对于屏幕左边和上边的位置。Firefox 则在 screenX 和 screenY 属性中提供相同的窗口位置信息,Safari 和 Chrome 也同时支持这两个属性。Opera虽然也支持 screenX 和 screenY 属性但与 screenLeft 和 screenTop 属性并不对应,因此建议大 家不要在 Opera 中使用它们。使用下列代码可以跨浏览器取得窗口左边和上边的位置。

var leftPos = (typeof window.screenLeft == "number") ?
                  window.screenLeft : window.screenX;
var topPos = (typeof window.screenTop == "number") ?
                  window.screenTop : window.screenY;

moveTo()接收的是新位置的 x 和 y 坐标值,

moveBy()接收的是在水平和垂直方向上移动的像素数。

//将窗口移动到屏幕左上角 
window.moveTo(0,0);
//将窗向下移动 100 像素 
window.moveBy(0,100);
//将窗口移动到(200,300) 
window.moveTo(200,300);
//将窗口向左移动 50 像素 
window.moveBy(-50,0);

窗口大小

跨浏览器确定一个窗口的大小不是一件简单的事。IE9+、Firefox、Safari、Opera 和 Chrome 均为此提 供了 4 个属性:innerWidthinnerHeightouterWidth 和 outerHeight。在 IE9+、Safari 和 Firefox中,outerWidth 和 outerHeight 返回浏览器窗口本身的尺寸(无论是从最外层的 window 对象还是从 某个框架访问)。在 Opera 中,这两个属性的值表示页面视图容器的大小。而 innerWidth 和 innerHeight则表示该容器中页面视图区的大小(减去边框宽度)。在 Chrome 中,outerWidth、outerHeight 与innerWidth、innerHeight 返回相同的值,即视口(viewport)大小而非浏览器窗口大小。

使用 resizeTo()和 resizeBy()方法可以调整浏览器窗口的大小

resizeTo()接收浏览器窗口的新宽度和新高度

resizeBy()接收新窗口与原窗口的宽度和高度之差

//调整到 100×100 
window.resizeTo(100, 100);
//调整到 200×150 
window.resizeBy(100, 50);
//调整到 300×300 
window.resizeTo(300, 300)

间歇调用和超时调用

setTimeout()方法,它接受两个参数:要执行的代码和以毫秒 表示的时间(即在执行代码前需要等待多少毫秒)

//推荐的调用方式 
setTimeout(function() {
    alert("Hello world!");
}, 1000);

指定的时间尚未过去之前调用 clearTimeout(),就可以完全取消超时调用

//设置超时调用
var timeoutId = setTimeout(function() {
    alert("Hello world!");
}, 1000);
//注意:把它取消 
clearTimeout(timeoutId);

setInterval()按照指定的时间间隔重复执行代码,直至间歇调用被取消或 者页面被卸载

//推荐的调用方式 
setInterval (function() {
    alert("Hello world!");
}, 10000);

clearInterval()方法并传入相应的间歇调用 ID。取消间歇调用的重要性要远远高于取消超时调用

var num = 0;
var max = 10;
var intervalId = null;
function incrementNumber() {
    num++;
    //如果执行次数达到了 max 设定的值,则取消后续尚未执行的调用 
    if (num == max) {
        clearInterval(intervalId);
        alert("Done");
    }
}
intervalId = setInterval(incrementNumber, 500);

系统对话框

浏览器通过 alert()confirm()和 prompt()方法可以调用系统对话框向用户显示消息

alert()方法的结果就是向用户显示一个系统对话框,其中包含指定的文本和一个 OK(“确定”)按钮。

confirm()方法生成的。从向用户显示消息的方面来看,这种“确认”对话 框很像是一个“警告”对话框。但二者的主要区别在于“确认”对话框除了显示 OK 按钮外,还会显示 一个 Cancel(“取消”)按钮,两个按钮可以让用户决定是否执行给定的操作。为了确定用户是单击了 OK 还是 Cancel,可以检查 confirm()方法返回的布尔值:true 表示单击了 OK,false 表示单击了 Cancel 或单击了右上角的 X 按钮

 

if (confirm("Are you sure?")) {
    alert("I'm so glad you're sure! ");
} else {
    alert("I'm sorry to hear you're not sure. ");
}

prompt()方法接受两个参数:要显示给用户的文本提示和文本输入域的默认值(可以是一个空字符串)。

var result = prompt("What is your name? ", "");
if (result !== null) {
    alert("Welcome, " + result);
}

location对象

下表列出了 location 对象的所有属性(注:省略了每个属 性前面的 location 前缀)。

查询字符串参数

尽管 location.search 返回从问号到 URL 末尾的所有内容,但却没有办法逐个 访问其中的每个查询字符串参数。为此,可以像下面这样创建一个函数,用以解析查询字符串,然后返 回包含所有参数的一个对象:

function getQueryStringArgs(){
    //取得查询字符串并去掉开头的问号
    var qs = (location.search.length > 0 ? location.search.substring(1) : ""),
    
    //保存数据的对象 
    args = {},

    //取得每一项
    items = qs.length ? qs.split("&") : [], 
    item = null,
    name = null
    value = null,
    
    //在 for 循环中使用
    i = 0,
    len = items.length;

    //逐个将每一项添加到 args 对象中 
    for (i=0; i < len; i++){
          item = items[i].split("=");
          name = decodeURIComponent(item[0]);
          value = decodeURIComponent(item[1]);
          if (name.length) {
              args[name] = value;
          }
    }
    return args;
}

使用上述方法:

//假设查询字符串是?q=javascript&num=10 
var args = getQueryStringArgs();
alert(args["q"]);   //"javascript"
alert(args["num"]); //"10"

位置操作

assign()方法为其传递一个 URL。这样,就可以立即打开新 URL 并在浏览器的历史记录中生成一条记录

location.assign("http://www.wrox.com");
//===
window.location = "http://www.wrox.com";
//===
location.href = "http://www.wrox.com";


//假设初始 URL 为 http://www.wrox.com/WileyCDA/

//将 URL 修改为"http://www.wrox.com/WileyCDA/#section1"
location.hash = "#section1";

//将 URL 修改为"http://www.wrox.com/WileyCDA/?q=javascript" 
location.search = "?q=javascript";

//将 URL 修改为"http://www.yahoo.com/WileyCDA/" 
location.hostname = "www.yahoo.com";

//将 URL 修改为"http://www.yahoo.com/mydir/" 
location.pathname = "mydir";

//将 URL 修改为"http://www.yahoo.com:8080/WileyCDA/" 
location.port = 8080;

//每次修改 location 的属性(hash 除外),页面都会以新 URL 重新加载。

replace()方法。这个方法 只接受一个参数,即要导航到的 URL;结果虽然会导致浏览器位置改变,但不会在历史记录中生成新记录,用户不能回到前一个页面

navigator对象

检测插件

检测浏览器中是否安装了特定的插件是一种最常见的检测例程。对于非 IE 浏览器,可以使用plugins 数组来达到这个目的。该数组中的每一项都包含下列属性。

  • name:插件的名字。
  • description:插件的描述。
  • filename:插件的文件名。
  • length:插件所处理的 MIME 类型数量。
//检测插件(在 IE 中无效) 
function hasPlugin(name){
    name = name.toLowerCase();
    for (var i=0; i < navigator.plugins.length; i++){
        if (navigator. plugins [i].name.toLowerCase().indexOf(name) > -1){ 
            return true;
        } 
    }
    return false;
}

//检测 Flash 
alert(hasPlugin("Flash"));

//检测 QuickTime 
alert(hasPlugin("QuickTime"));

history对象

go()方法这个方法接受一个参数, 若是整数值则表示向后或向前跳转的页面数,若是字符串则会跳转到历史记录中包含该字符串的第一个 位置——可能后退,也可能前进

//后退一页 
history.go(-1);
//前进一页 
history.go(1);
//前进两页 
history.go(2);
//跳转到最近的 wrox.com 页面 
history.go("wrox.com");
//跳转到最近的 nczonline.net 页面 
history.go("nczonline.net");

简写方法 back()和 forward()来代替 go()

//后退一页
history.back();
//前进一页 
history.forward();

客户端检测

以下是完整的用户代理字符串检测脚本,包括检测呈现引擎、平台、Windows 操作系统、移动设备和游戏系统

var client = function(){
    //呈现引擎
    var engine = {
        ie: 0,
        gecko: 0,
        webkit: 0,
        khtml: 0,
        opera: 0,

        //完整的版本号
        ver: null 
    };
    //浏览器
    var browser = {
        //主要浏览器 
        ie: 0, 
        firefox: 0, 
        safari: 0, 
        konq: 0, 
        opera: 0,
        chrome: 0,

        //具体的版本号
        ver: null 
    };
    //平台、设备和操作系统 
    var system = {
        win: false,
        mac: false,
        x11: false,

        //移动设备
        iphone: false, 
        ipod: false, 
        ipad: false,
        ios: false, 
        android: false, 
        nokiaN: false, 
        winMobile: false,

        //游戏系统 
        wii: false, 
        ps: false
    };
    //检测呈现引擎和浏览器
    var ua = navigator.userAgent; 
    if (window.opera){
        engine.ver = browser.ver = window.opera.version();
        engine.opera = browser.opera = parseFloat(engine.ver);
    } else if (/AppleWebKit\/(\S+)/.test(ua)){
        engine.ver = RegExp["$1"];
        engine.webkit = parseFloat(engine.ver);

        //确定是 Chrome 还是 Safari
        if (/Chrome\/(\S+)/.test(ua)){
            browser.ver = RegExp["$1"];
            browser.chrome = parseFloat(browser.ver);
        } else if (/Version\/(\S+)/.test(ua)){
            browser.ver = RegExp["$1"];
            browser.safari = parseFloat(browser.ver);
        } else {
            //近似地确定版本号
            var safariVersion = 1;
            if (engine.webkit < 100){
                safariVersion = 1;
            } else if (engine.webkit < 312){
                safariVersion = 1.2;
            } else if (engine.webkit < 412){
                safariVersion = 1.3;
            } else {
                safariVersion = 2;
            }
            browser.safari = browser.ver = safariVersion;
        }
    } else if (/KHTML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){  
        engine.ver = browser.ver = RegExp["$1"];
        engine.khtml = browser.konq = parseFloat(engine.ver);
    } else if (/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){
        engine.ver = RegExp["$1"];
        engine.gecko = parseFloat(engine.ver);
    //确定是不是 Firefox
    if (/Firefox\/(\S+)/.test(ua)){
        browser.ver = RegExp["$1"];
        browser.firefox = parseFloat(browser.ver);
    }
    } else if (/MSIE ([^;]+)/.test(ua)){
        engine.ver = browser.ver = RegExp["$1"];
        engine.ie = browser.ie = parseFloat(engine.ver);
    }

    //检测浏览器
    browser.ie = engine.ie; 
    browser.opera = engine.opera;

    //检测平台
    var p = navigator.platform;
    system.win = p.indexOf("Win") == 0;
    system.mac = p.indexOf("Mac") == 0;
    system.x11 = (p == "X11") || (p.indexOf("Linux") == 0);


    //检测 Windows 操作系统 
    if (system.win){
        if (/Win(?:dows )?([^do]{2})\s?(\d+\.\d+)?/.test(ua)){
            if (RegExp["$1"] == "NT"){
                switch(RegExp["$2"]){
                    case "5.0":
                        system.win = "2000";
                        break;
                    case "5.1":
                        system.win = "XP";
                        break;
                    case "6.0":
                        system.win = "Vista";
                        break;
                    case "6.1":
                        system.win = "7";
                        break;
                    default:
                        system.win = "NT";
                        break; 
                }
            } else if (RegExp["$1"] == "9x"){
                system.win = "ME";
            } else {
                system.win = RegExp["$1"];
            } 
        }
    }

    //移动设备
    system.iphone = ua.indexOf("iPhone") > -1;
    system.ipod = ua.indexOf("iPod") > -1;
    system.ipad = ua.indexOf("iPad") > -1;
    system.nokiaN = ua.indexOf("NokiaN") > -1;
    //windows mobile
    if (system.win == "CE"){
         system.winMobile = system.win;
    } else if (system.win == "Ph"){
        if(/Windows Phone OS (\d+.\d+)/.test(ua)){
            system.win = "Phone";
            system.winMobile = parseFloat(RegExp["$1"]);
        } 
    }

    //检测 iOS 版本
    if (system.mac && ua.indexOf("Mobile") > -1){
        if (/CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)){
           system.ios = parseFloat(RegExp.$1.replace("_", "."));
        } else {
           system.ios = 2; //不能真正检测出来,所以只能猜测
        } 
    }

    //检测 Android 版本
    if (/Android (\d+\.\d+)/.test(ua)){
        system.android = parseFloat(RegExp.$1);
    }

    //游戏系统
    system.wii = ua.indexOf("Wii") > -1; 
    system.ps = /playstation/i.test(ua);

    //返回这些对象 return {
         engine:     engine,
         browser:    browser,
         system:     system
    };
}();

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值