当前位置:   article > 正文

《javascript高级程序设计》读书笔记

《javascript高级程序设计》读书笔记

javascript 学习笔记

标签(空格分隔): JavaScript js


函数

arguments和this

函数内部的两个特殊对象:arguments和this
arguments时类数组对象,保存着传入函数中的所有参数。
arguments对象有一个名叫callee的属性,这个属性是一个指针,指向函数本身(取消函数名紧密耦合的现象)

this详解

随着函数使用场合的不同,this的值会发生变化。但是有一个总的原则,那就是this指向的是调用函数的那个对象。
但是apply()方法可以改变函数的调用对象,它的第一个参数就表示改变后的调用这个函数的对象。因此this的值会发生改变。

属性caller:保存着调用当前函数的函数的引用。

每个函数都有两个属性:length和prototype。length表示函数希望接受的命名参数的个数。

apply call和bind方法

每个函数都有两个非继承而来的方法:apply()和call(),这两个方法的用途是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。apply()方法接受两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array的实例,也可以是arguments对象。

call()方法与apply()方法的作用相同,它们的区别仅仅在于接受参数的方式不同。对于call()方法而言,第一个参数是this值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来。

call()和apply()方法可以用于扩充对象的作用域。这一点最大的好处在于,对象不需要和方法有任何耦合关系。

ECMAScript5还定义了一个方法bind()。这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。

基本包装类型

基本包装类型与引用类型类似。实际上,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。
引用类型与基本包装类型的主要区别就是对象的生存期。使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。
自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁。

Boolean对象

尽量不要使用Boolean对象。

var falseObject=new Boolean(false);
var result=falseObject && true;
alert(result); // true

var falseValue=false;
result=falseValue && true;
alert(result); // false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这个例子中,我们使用了false值创建了一个Boolean对象,然而在falseObject && true这个式子中,我们是对falseObject本身求值,布尔表达式中所有对象都会被转为true。
这个结果可能会造成误解,最好不要使用。

Number类型

toFixed(n) 按照指定的小数位返回数值的字符串(自动四舍五入)
toExponential(n) 指定有效数字的小数位数,e输入法(同样自动四舍五入)
toPrecision(n) 按照位数自动选择是否采用科学计数法

String类型

length属性,表示字符串中包含多少个字符
PS:需要注意的是字符串中即使有非ASCII字符(汉字),length属性表示的依然是有多少个字符(不会翻倍)
charAt(n)方法 等价于string[n]
charCodeAt(n)方法 输出字符编码

字符串操作方法

string方法:
concat(str) 将目标和str连接起来
concat()可以接受多个参数,会将所有参数都与目标相接(等同于加号)

slice()、substr()、substring()方法。
第一个参数指定子字符串开始位置。第二个表示子字符串在哪里结束。
slice()和substring()的第二个参数指定的是子字符串最后一个字符后面的位置。而substr()的第二个参数指定的是返回的字符个数。

字符串位置方法

indexOf()和lastIndexOf()。这两个方法都是从一个字符串中搜索给定的子字符串,然后返回子字符串的位置(没有找到返回-1)
可以接受第二个参数,表示从字符串的哪个位置开始搜索

trim

trim()方法会创建一个字符串的副本,删除前置和后缀的所有空格

模式匹配

第一个:match()
match()方法只接受一个参数,要么是一个正则表达式,要么是一个RegExp对象。
match()方法返回一个数组,等价于调用RegExp对象的exec()方法并传递本例中的字符串作为参数。
第二个:search()。这个方法的唯一参数与match()方法的参数相同:由字符串或RegExp对象指定的一个正则表达式。只返回第一个匹配项的索引

替换

replace()方法,接受两个参数:第一个参数可以是一个字符串,或是一个RegExp对象,或是一个函数。如果第一个参数是字符串,那么只会替换第一个子字符串。要想替换所有子字符串,唯一的方法就是提供一个正则表达式,而且要指定全局标志(g)

split

split()方法可以基于指定的分隔符将一个字符串分割成多个子字符串,并将结果放在一个数组中。分隔符可以是一个字符串或是一个RegExp对象(注:这个方法不会将字符串看作正则表达式)
split()方法可以接受第二个参数,用于指定数组的大小,以便确认返回的数组不会超过既定大小。

fromCharCode()

静态方法:fromCharCode()的任务是接受一或多个字符编码,然后将它们转换成一个字符串

单体内置对象

定义:由ECMAScript实现提供的、不依赖于宿主环境的对象,这些对象在ECMAScript程序执行之前就已经存在了。

Global对象

不属于其他对象的属性和方法最终都是Global对象的属性和方法。
所有全局作用域中定义的属性和函数,都是Global对象的属性。

URI编码方法

Global对象的encodeURI()和encodeURIComponent()方法可以对URI进行编码,以便发送给浏览器。
其中,encodeURI()主要用于整个URI,而encodeURIComponent()主要用于对URI中的某一段进行编码。encodeURI()不会对本身属于URI的特殊字符进行编码(冒号、正斜杠、问号和井号);encodeURIComponent()会对它发现的所有非标准字符进行编码。
对应的两个方法是decodeURI()和decodeURIComponent()

eval()方法

eval(str)可以将任意str作为命令执行,执行环境为当前环境。
可以用于JSON类型数据的处理。

在Web浏览器中,Global对象作为window对象的一部分进行实现。

Math对象

min和max方法

接受多个参数,返回最大/最小值

舍入方法

Math.ceil()执行向上舍入
Math.floor()执行向下舍入
Math.round()执行标准舍入(四舍五入)

Random

Math.random()方法返回介于0和1之间的一个随机数

function selectFrom(lowerValue,upperValue)
{
    var choices=upperValue-lowerValue+1;
    return Math.floor(Math.random()*choices+lowerValue);
}
  • 1
  • 2
  • 3
  • 4
  • 5

selectFrom()接受两个参数:应该返回的最小值和最大值。以此返回一个在其中范围的随机数

面向对象

ECMAScript中有两种属性:数据属性和访问器属性

对象属性:数据属性与访问器属性

1.数据属性:数据属性包括一个数据值的位置。这个位置可以读取和写入值。数据属性有4个描述其行为的特性。

[[Configurable]]:是否能通过delete删除属性从而重新定义属性,或者能否修改属性的特性,或者能否把属性修改为访问器属性。默认为true
[[Enumerable]]:表示能否通过for-in循环返回属性。默认为true.
[[Writable]]:表示能否修改属性的值。默认为true.
[[Value]]:包含这个属性的数据值。
  • 1
  • 2
  • 3
  • 4

要修改属性默认的特性必须使用ECMAScript5的Object.defineProperty()方法。这个方法接受三个参数:属性所在的对象、属性的名字和一个描述符对象。其中,描述符对象的属性必须是:configurable、enumerable、writable和value。设置其中的一个或多个值,可以修改对应的特性值。

2.访问器属性:包含一对getter和setter函数,在读取访问器属性时,会调用getter函数,这个函数负责返回有效的值;在写入访问器属性时,会调用setter函数并传入新值,这个函数负责决定如何处理数据。

4个特性

[[Configurable]]表示是否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。默认为true
[[Enumerable]]表示是否通过for-in循环返回属性。默认为true
[[Get]] 在读取属性时调用的函数。默认undefined
[[Set]] 在写入属性时调用的函数。默认undefined
  • 1
  • 2
  • 3
  • 4

PS:通过在属性前加下划线,表示只能通过对象方法访问的属性
例如:

var book={
    _year: 2004,
    edition: 1
};

Object.defineProperty(book,"_year",{
    get: function(){
        return this._year;
    },
    set: function(newValue){
        if(newValue>2004){
            this._year=newValue;
            this.edition+=newValue-2004;
        }
    }
});

book.year=2005;
alert(book.editoin); //2
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

访问器属性year包含一个getter函数和一个setter函数。getter函数返回_year的值,setter函数通过计算来确认正确的版本。

即设置一个属性的值会导致其他属性发生变化。

PS:不一定非要同时指定getter和setter。

读取属性的特性

使用ECMAScript 5的Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。这个方法接受两个参数:属性所在对象和要读取其描述符的属性名称。返回值是一个对象。如果是访问器属性,这个对象的属性有configurable、enuerable、get和set;如果是数据属性,这个对象的属性有configurable、enumerable、writable和value。

创建对象

工厂模式

使用函数(工厂)来创建对象(产品)
例如:

function createPerson(name,age,job){
    var o=new Object();
    o.name=name;
    o.age=age;
    o.job=job;
    o.sayName=function(){
        alert(this.name);
    };
    return o;
}

var person1=createPerson("Nicholas",29,"Software");
var person2=createPerson("Greg",27,"Doctor");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

问题:工厂模式虽然解决了创建多个相似对象的问题,却没有解决对象识别问题(即怎么知道一个对象的类型)

构造函数模式

将上面的例子重写如下:

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=function(){
        alert(this.name);
    };
}

var person1=new Person("Nicholas",29,"Software");
var person2=new Person("Greg",27,"Doctor");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

不同之处:
1.没有显示地创建对象
2.直接将属性和方法赋给了this对象
3.没有return语句。
惯例:构造函数始终以一个大写字母开头。

要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:
1.创建一个新对象;
2.将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
3.执行构造函数中的代码
4.返回新对象

在最后person1和person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person,如下所示。
alert(person1.constructor == Person); // true
对象的constructor属性最初是用来标识对象类型的。但是,提到检测对象类型,还是instanceof 操作符更可靠。
alert(person1 instanceof Person); // true

创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型。

构造函数的问题

使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。
比如以上文为例,不同实例中的同名函数实际上是不相等的。
alert(person1.sayName == person2.sayName); // false
解决方法是通过把函数定义转移到构造函数外部来解决这个问题。

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=sayName;
}
function sayName(){
    alert(this.name);
}

var person1=new Person("Nicholas",29,"Software");
var person2=new Person("Greg",27,"Doctor");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

新问题:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。而且如果对象需要定义很多方法,那么就要定义很多个全局函数,于是这个自定义的引用类型就丝毫没有封装性可言了。、

原型模式

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。字面意思,prototype就是通过调用构造函数而创建的那个对象实例的原型对象。

好处:所有对象实例共享它所包含的属性和方法。
例子:

function Person(){
}

Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="Software Engineer";
Person.prototype.sayName= function(){
    alert(this.name);
};

var person1=new Person();
person1.sayName();

var person2=new Person();
person2.sayName();

alert(person1.sayName == person2.sayName); // true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

虽然构造函数是一个空函数,但仍然可以通过调用构造函数来创建新对象,而且新对象还会具有相同的属性和方法。但与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的。

理解原型对象

无论何时,只要创建了一个新函数,就会自动该函数创建一个prototype属性,这个属性指向函数的原型对象。
默认情况下,所有原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针。例如:Person.prototype.constructor指向Person。
连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。
虽然无法访问[[Prototype]],但可以通过isPrototypeOf()方法来确认对象之间是否存在这种关系。从本质上讲,如果[[Prototype]]指向调用isPrototypeOf()方法的对象(Person.prototype),那么这个方法就返回true。如下所示:
alert(Person.prototype.isPrototypeOf(person1));

ECMAScript 5增加了一个新方法Object.getPrototypeOf(),可以返回[[Prototype]]的值。
例如:
alert(Object.getPrototypeOf(person1) == Person.prototype); // true
alert(Object.getPrototypeOf(person1).name);//"Nicholas"

虽然可以通过对象实例访问保存在原型中的值,但却不能通过对戏那个实例重写原型中的值。如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。
使用delete操作符可以完全删除实例属性,从而让我们能够重新访问原型中的属性,例如:

function Person(){
}

Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};

var person1=new Person();
var person2=new Person();

person1.name="Greg";
alert(person1.name);//Greg 来自实例
alert(person2.name);//Nicholas 来自原型

delete person1.name;
alert(person1.name); // Nicholas 来自原型
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

使用hasOwnProperty()方法可以检测一个属性是否存在于实例中,还是存在于原型中。

原型与in操作符

单独使用in操作符时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。

通过上面两个函数可以判断属性是存在于对象中,还是存在于原型中。

function hasPrototypeProperty(object,name){
    return !object.hasOwnProperty(name)&& (name in object);
}
  • 1
  • 2
  • 3
  • 4
Object.keys()

这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

如果想要得到所有的实例属性,无论它是否可枚举,都可以使用Object.getOwnPropertyNames()

更简单的原型语法
function Person(){
}

Person.prototype = {
    constructor : Person, //如果constructor的值很重要,可以刻意设置回适当的值
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function() {
        alert(this.name);
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

最终结果基本相同,但constructor属性不再指向Person了(指向Object构造函数)

也可以用Object.defineProperty()

Object.defineProperty(Person.prototype, "constructor",{
    enumerable: false,
    value : Person
});
  • 1
  • 2
  • 3
  • 4
原型对象的问题

1.省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。
2.原型中的所有属性都是被很多实例共享的。但如果属性包含了引用类型值,则会导致意想不到的情况。

function Person(){
}

Person.prototype= {
    constructor : Person,
    name :"Nicholas", 
    age: 29,
    job: "Software Engineer",
    friends : ["Shelby","Court"],
    sayName : function() {
        alert(this.name);
    }
};

var person1= new Person();
var person2= new Person();

person1.friends.push("Van");

alert(person1.friends);//"Shelby,Court,Van"
alert(person2.friends);//"Shelby,Court,Van"
alert(person1.friends==person2.friends); // true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
组合使用构造函数模式和原型模式

创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。

构造函数模式用于定义实例属性,原型模式用于定义实例的方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用来最大程度地节省内存。

同时,这种混成模式支持向构造函数传递参数;可谓是集两种模式之长。

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.friends=["Shelby","Court"];
}

Person.prototype = {
    constructor : Person,
    sayName : function() {
        alert(this.name);
    }
}

var person1 = new Person("Nicholas",29,"Software Engineer");
var person2 = new Person("Greg",27,"Doctor");

person1.friends.push("Van");
alert(person1.friends);//"Shelby,Count,Van"
alert(person2.frineds);//"Shelby,Count"
alert(person1.friends === person2.friends);//false
alert(person1.sayName === person2.sayName );//true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
动态原型模式

把所有信息都封装到了构造函数中,通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。

可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

function Person(name,age,job){
    this.name=name;
    this.job=job;
    this.age=age;
    if(typeof this.sayName != "function"){
        Person.prototype.sayName = function(){
            alert(this.name);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

明显if中的代码只会在初次调用构造函数的时候才会执行,此后,原型已经完成初始化,不需要再做修改了。

寄生构造函数模式

基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。

function Person(name,age,job){
    var o=new Object();
    o.name=name;
    o.age=age;
    o.job=job;
    o.sayName = function(){
        alert(this.name);
    };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

看上去与构造函数十分相似,实际上与工厂模式是一模一样的。

稳妥构造函数模式

稳妥对象(durable objects)指的是没有公共属性,而且其方法也不引用this的对象。

稳妥对象适合用在一些安全的环境中(这些环境会禁止使用this和new),或者在防止数据被其他应用程序改动时使用。

稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一时新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。

function Person(name,age,job){
    var o=new Object();
    o.sayName = function(){
        alert(name); // 不引用this
    };

    return o;
}

//使用
var friend = Person("Nicholas",29,"Software Engineer");
friend.sayName(); //除了sayName外没有任何方法可以访问到name的值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

继承

ECMAScript的实现继承主要是依靠原型链来实现的。

原型链

基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。

function SuperType(){
    this.property= true;
}

function SubType(){
    this.subproperty=false;
}
SubType.prototype = new SuperType();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

通过原型链实现继承之后,不能使用对象字面量创建原型方法。因为这样做就会重写原型链。

SubType.prototype = {
    getSubValue : function(){
        return this.subproperty;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5

原型链的问题:在创建子类型的实例时,不能向超类型的构造函数中传递参数。

借用构造函数

constructor stealing 有时也叫伪造对象或经典继承。

基本实习那个:在子类型构造函数的内部调用超类型构造寒素。(使用apply和call方法)

function SuperType(){
    this.colors = ["red","blue","green"];
}

function SubType(){
    SuperType.call(this);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

借用构造函数可以在子类型构造函数中向超类型构造函数传递参数

function SuperType(name){
    this.name=name;
}

function SubType(){
    SuperType.call(this,"Nicholas");
    this.age=29;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

问题:无法进行函数复用。而且超类型的原型中定义的方法对于子类型而言也是不可见的。

组合继承

最经常使用的继承方式。

combination inheritance 伪经典继承,指的是将原型链和借用构造函数的技术组合到一起,发挥两者之长的继承模式。

思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

function SuperType(name){
    this.name=name;
    this.colors = ["red","blue","green"];
}

SuperType.prototype.sayName = function(){
    alert(this.name);
};

function SubType(name,age){
    SuperType.call(this,name);
    this.age=age;
}

SubType.prototype = new SuperType();

SubType.prototype.sayAge = function (){
    alert(this.age);
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
原型式继承

ECMAScript5通过新增Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。

第二个参数与Object.defineProperties()方法的参数格式相同。例如

var person = {
    name : "Nicholas",
    friends  : ["Shelby","Court","Van"]
};

var anotherPerson = Object.create(person,{
    name :{
        value : "Greg"
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。

寄生式继承

创建一个仅用于封装继承过程的函数,在内部以某种方式来增强对象,最后返回对象。

function createAnother(original){
    var clone = object(original);
    clone.sayHi = function () {
        alert("hi");
    };
    return clone;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
寄生组合式继承

组合继承的不足:无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

寄生组合式继承的基本模式

function inheritPrototype(subType,superType){
    var prototype = object(superType.prototype);//创建超类型原型的一个副本
    prototype.constructor =subType;//为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性
    subType.prototype=prototype;//将新创建的对象赋值给子类型的原型
}
  • 1
  • 2
  • 3
  • 4
  • 5

函数表达式

两个创建函数的方式:
1.函数声明

function functionName(arg0,arg1,arg2){
    //函数体
}
  • 1
  • 2
  • 3

同时可以通过非标准的name属性返回跟在function关键字后面的标识符。

函数声明的一个重要特征就是函数声明提升,意识是在执行代码之前会先读取函数声明。这意味着可以把函数声明放在调用它的语句后面。

sayHi();
function() sayHi(){
    alert("Hi!");
}
  • 1
  • 2
  • 3
  • 4

2.函数表达式

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

此时创建的函数叫做匿名函数(anonymous function)或lamda函数

递归与取消名字绑定

js中的函数中函数名可以指向不一样的函数,它只是一个指向函数对象的指针。

//经典的阶乘函数
function factorial(num){
    if(num<=1){
        return 1;
    }
    else{
        return num * factorial(num-1);
    }
}

//出错
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));//出错
//因为函数中的操作与factorial绑定,如果原函数发生改变,会使得所有基于此函数的函数不能正常工作。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

解决方法:

function factorial(num){
    if(num<=1){
        return 1;
    } else{
        reutrn num * arguments.callee(num-1);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

或是这样

var factorial = (function f(num){
    if(num<=1){
        return 1;
    } else {
        return num * f(num-1);
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

闭包

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

按值传递

function createFunctions(){
    var result = new Array();

    for(var i=0; i<10; i++){
        result[i] = function(num){
            return function(){
                return num;
            };
        }(i);//将i的值传给num
    }

    return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
this对象

this对象是在运行时基于函数的执行环境绑定的。匿名函数的执行环境具有全局性,因此其this对象通常指向window。

不过,把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。

var name = "The Window";

var object = {
    name : "My Object",

    getNameFunc : function(){
        var that=this;
        return function(){
            return that.name;
        };
    }
};

alert(object.getNameFunc());// "My Object"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
内存泄漏

闭包在IE的一些版本(IE9以前)会出现一些特殊的问题。具体来说,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。

模仿块级作用域

如前所述,JavaScript没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。

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

    var i;
    alert(i);//num
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

JavaScript从来不会告诉你是否多次声明了同一个变量;遇到这种情况,它只会对后续的声明视而不见。匿名函数可以用模仿块级作用域来避免这个问题。

(function(){
    //块级作用域
})();
  • 1
  • 2
  • 3

以上代码定义并立即调用了一个匿名函数。将函数声明包含在一堆圆括号内,表示它实际上是一个函数表达式。匿名函数中定义的任何变量都会在执行结束后被销毁,从而实现了私有作用域。

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

    alert(i);//这将导致一个错误!
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。

私有变量

严格来讲,JavaScript中没有私有成员的概念,但是有一个私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。

私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。

特权方法

我们把有权访问私有变量和私有函数的公有访问称为特权方法。有两种在对象上创建特权方法的方式。

第一种是在构造函数中定义特权方法

function MyObject(){

    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }

    //特权方法
    this.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这样私有变量和私有函数的访问就只能通过特权方法进行了。利用私有和特权成员,可以隐藏那些不应该被直接修改的数据。

静态私有变量
(function(){
    //私有变量和私有函数
    function PrivateFunction(){
        return false;
    }

    //构造函数
    MyObject = function(){
    };

    //公有/特权方法
    MyObject.prototype.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };
})();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

PS:函数声明只能创建局部函数,所以使用了函数表达式。
这个模式与在构造函数中定义特权方法的主要区别在于,私有变量和函数是由实例共享的。由于特权方法是在原型上定义的,因此所有实例都使用同一个函数。

这种模式下构造的函数所影响的私有变量同样会在所有实例中共享,由此而产生的变量就是静态私有变量。
PS:多查找作用域中的一个层次,就会在一定程度上影响查找速度,这正是使用闭包和私有变量的一个显明的不足之处。

模块模式

前面的模式是用于为自定义类型创建私有变量和特权方法,而模块模式则是为单例创建私有变量和特权方法。

JavaScript是以对象字面量的方式来创建单例对象的。

var singleton = {
    name : value,
    method : function(){
        //代码
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

var singleton = function(){
    var privateVariable = 10;

    function privateFunction(){
        return false;
    }

    return {
        public Property : true;
        publicMethod : function() {
            privateVariable++;
            return privateFunction();
        }
    };
}();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
增强的模块模式
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;
}();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

BOM

window

BOM的核心作用域就是window,它表示浏览器的一个实例,同时作为JavaScript访问浏览器的一个接口和ECMAScript规定的Global对象。

全局作用域

在全局作用域中定义的任何变量和函数都会被自动归在window对项目名下。但定义全局变量和在window对象上直接定义属性还是有一点差别: 全局变量不能通过delete操作符删除,而直接在window对象上定义的属性可以。

另外,尝试访问未声明的变量会抛出错误,但是通过查询window对象,可以知道某个可能未声明的变量是否存在。例如:

var newValue = oldValue; //这里会抛出错误,因为oldValue未定义

//这里不会抛出错误,因为这是一次属性查询。
//newValue的值是undefined
var newValue = window.oldValue;
  • 1
  • 2
  • 3
  • 4
  • 5
框架

top指代最高层的框架,也就是浏览器窗口。因为对于任何一个在框架中完成的代码而言,其中window指向的都是那个框架的特定实例。

parent指向的是当前框架的直接父框架。

self对象始终指向window

窗口位置

浏览器提供了screenLeft和screenTop属性,分别用于表示窗口相对于屏幕左边和上边的位置。(Firefox在screenX和screenY属性中提供相同的窗口位置信息)。Opera的两组属性不对应,不建议使用。
跨浏览器取得窗口左边和上边的位置的代码

var leftPos = (typeof window.screnLeft == "number") ? window.screenLeft : window.screenX;
var topPos = (typeof window.screenTop == "number") ? window.screenTop : window.screenY;
  • 1
  • 2
移动方法

moveTo()接受两的是新位置的x和y坐标值,moveBy()接收的是在水平和垂直方向上移动的像素数。
PS:在非IE浏览器下,窗口对象必须是window.open出来的才能够执行这些方法。

窗口大小

4个属性:innerWidth、innerHeight、outerWidth和outerHeight。
其中outerWidth和outerHeight返回浏览器窗口本身的尺寸,而innerWidth和innerHeight则表示该容器中页面视图区的大小。
页面视口的大小可以通过document.docuemntElement.clientWidthdocument.documentElement.clientHeight来获取。
使用resizeTo()和resizeBy()方法可以调整浏览器窗口的大小。其中resizeTo()接收浏览器窗口蛾新宽度和新高度,而resizeBy()接收新窗口与原窗口的宽度和高度之差

打开窗口

window.open()方法可以导航到一个既定的URL或是打开一个新的浏览器窗口。这个方法可以接收4个参数:要加载的URL、窗口目标、一个特性字符串以及表示新页面是否取代浏览器历史记录中当前加载页面的布尔值。

第二个参数可以是下列任何一个特殊的窗口名称:_self、_parent、_top、_blank。

弹出窗口

如果window.open()传递的第二个参数并不是一个已经存在的窗口或框架,那么该方法就会根据第三个参数位置上传入的字符串创建一个新窗口或标签页。

设置方法
fullscreenyes/no浏览器窗口是否最大化,仅限IE
heightnumber新窗口高度,不低于100
leftnumber新窗口左坐标,不能为负
locationyes/no是否在浏览器窗口中显示地址栏
menubaryes/no是否在浏览器窗口中显示菜单栏
resizableyes/no是否可以通过拖动浏览器窗口的边框改变其大小
scrollbarsyes/no如果内容在视口中显示不下,是否允许滚动
statusyes/no是否显示状态栏
toolbaryes/no是否显示工具栏
topnumber新窗口的上坐标,不能为负
widthnumber新窗口宽度,不能小于100

window.open()方法会返回一个指向新窗口的引用。引用的对象大致相似,但可以进行更多的控制。
同时调用close()方法还可以关闭新打开的窗口。

新创建的window对象有一个opener属性,其中保存着打开它的原始窗口对象。

屏蔽程序

如果浏览器扩展或是其他程序阻止弹出窗口,那么window.open()通常会抛出一个错误。

var blooked = false;
try{
    var wroxWin = window.open("http://www.wrox.com","_blank");
    if(wroxWin == null){
        blocked = true;
    }
}   catch(ex){
    blocked = true;
}

if(blocked){
    alert("Blocked!!");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

以上代码可以检查出任何情况下,弹出窗口是否被屏蔽。

间歇调用和超时调用

通过设置超时值和间歇时间值来调度代码在特定的时刻执行。前者是在指定的时间过后执行代码,后者是每隔指定的时间就执行一次代码。

超时调用需要使用window对象的setTimeout()方法,它接受两个参数:要执行的代码和以毫秒表示的时间。
第一个参数可以是一个包含JavaScript代码的字符串(eval函数),也可以是一个函数。
第二个参数是一个表示等待多长时间的毫秒数,但经过该时间后指定的代码不一定会执行,JavaScript是一个单线程的解释器,一定时间内只能执行一段代码。setTimeout()的第二个参数告诉JavaScript再过多长时间把当前任务添加到队列中。

调用setTimeout()后,该方法会返回一个数值ID,表示超时调用。这个调用ID是计划执行代码的唯一标识符,可以通过它来取消超时调用。(调用clearTimeout()方法,参数为调用ID)
只要在指定的时间尚未过去之前调用clearTimeout(),就可以完全取消超市调用。

间歇调用与超时调用类似,只不过它会按照指定的时间间隔重复执行代码,直至间歇调用被取消或者页面被卸载。使用setInterval()方法设置间歇调用,它接受的参数与setTimeout()相同:要执行的代码和每次执行之前需要等待的毫秒数。使用clearInterval()方法取消调用。

问题:在开发环境下很少使用真正的间歇调用,原因是后一个间歇调用可能会在前一个间歇调用结束之前启动。一般认为,用超时调用模拟间歇调用是一种最佳模式

系统对话框

alert()会生成“警告”对话框向用户显示一些他们无法控制的消息,例如错误消息。用户只能在看完消息后关闭对话框。

confirm()会让用户选择确认与否,可以检查confirm()方法返回的布尔值:true表示单击了ok,false表示单机了Cancel或单击了右上角的X按钮。

prompt()方法生成一个“提示”框,用于提示用户输入一些文本。提示框中除了显示OK和Cancel按钮之外,还会显示一个文本输入域,以供用户在其中输入内容。prompt()方法接受两个参数:要显示给用户的文本提示和文本输入域的默认值。

如果用户单击了ok按钮,则prompt()返回文本输入域的值;如果用户单击了Cancel或没有单击ok而是用其他方式关闭了对话框,这该方法返回null。

还有print()和find()对话框。这两个是异步对话框,等同于浏览器菜单的“查找”和“打印”命令。

location对象

location对象既是window对象的一个属性,又是document对象的属性。既window.location和document.location引用的是同一个对象。

属性名说明
hash返回URL中的hash($#后跟零或多个字符),如果URL不包含散列,则返回空字符串
host返回服务器名称和端口号(如果有)
hostname返回不带端口号的服务器名称
href返回当前加载页面的完整URL,而location对象的toString()方法也返回这个值
pathname返回URL中的目录和文件名
port返回URL中的指定的端口号。如果URL中不包含端口号,则这个属性返回空字符串
protocol返回页面使用的协议。通常是http:或https:
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
位置操作

使用location对象可以通过很多方式来改变浏览器的位置。最常用的方式就是使用assign()方法。location.assign("http://www.wrox.com");

这样会立即打开新URL并在浏览器的历史记录中生成一条记录。如果是将location.href或window.location设置为一个URL值,效果是一样的。

也可以通过修改location对象的其他属性来改变当前加载的页面。

当通过上述任何一种方式修改URL之后,浏览器的历史记录中就会生成一条新记录,用户通过单击“后退”按钮都会导航到前一个页面。要禁用这种行为可以使用replace()方法。这个方法接受要导航的URL,结果是浏览器位置改变,但不会在历史记录中生成新记录。

reload()方法,可以重新加载当前显示的页面。

location.reload();//重新加载,有可能从缓存中加载
location.reload(true); //重新加载,从服务器重新加载
  • 1
  • 2
属性或方法说明
appCodeName浏览器的名称,通常都是Mozilla(非Mozilla浏览器中也是如此)
appMinorVersion次版本信息
appName完整的浏览器名称
appVersion浏览器版本,一般不与实际对应
buildID浏览器编译版本
cookieEnabled表示cookie是否启用
cpuClass客户端cpu类型
javaEnabled()表示当前浏览器是否启用了java
language浏览器的主语言
mimeTypes浏览器中注册的MIME类型数组
onLine浏览器是否连接到因特网
oscpu客户端计算机的操作系统或使用的CPU
Platform浏览器所在的系统平台
plugins浏览器中安装的插件信息的数组
preference()设置用户的首选项
product产品名称
productSub产品的次要信息
register-ContentHandler()针对特定的MIME类型将一个站点注册为处理程序
register-ProtocolHandler()针对特定的协议将一个站点注册为处理程序
systemLanguage操作系统的语言
userAgent浏览器的用户代理字符串
userLanguage浏览器的默认语言
userProfile借以访问用户个人信息的对象
检测插件

可以使用plugins数组来达成目的,该数组的每一项都包含下列属性:

name:插件名称
description: 插件的描述
filename: 插件的文件名
length: 插件所处理的MIME类型数量
  • 1
  • 2
  • 3
  • 4
注册处理程序

registerContentHandler()和registerProtocolHandler()方法,可以让一个站点指明它可以处理特定类型的信息。

registerContentHandler()方法接收三个参数:要处理的MIME类型、可以处理该MIME类型的页面的URL以及应用程序的名称。
例如要将一个站点注册为处理RSS源的处理程序,可以使用如下代码
navigator.registerContentHandler("application/rss+xml","http://www.somereader.com?feed=%s","Some Reader");
第一个参数是RSS源的MIME类型。第二个参数是接收RSS源URL的URL,其中的%s表示RSS源URL,由浏览器自动插入。

screen对象

screen对象基本上用来表明客户端的能力,其属性通常集中出现在测定客户端能力的站点跟踪公众,但通常不会影响功能。
由于不同设备下的分辨率有所差异,可以通过访问screen对象获取准确的分辨率,从而调节挂件大小。

history对象

history对象保存着用户上网的历史记录,因为history是window对象的属性,所以每个浏览器窗口、每个标签页乃至每个框架,都有自己的history对象与特定的window对象关联。

go()方法可以在用户的历史记录中任意跳转

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

另外可以使用简写的back()和forward()方法来代替go(),顾名思义,这两个方法可以模仿浏览器的“后退”和“前进”按钮。

history对象还有一个length属性,保存着历史记录的数量。这个数量包括所有历史记录即所有向前和向后的记录。显然,对于加载的第一个页面而言,history.length == 0

客户端检测

虽然浏览器大部分都支持一组常用的公共功能,但不同浏览器之间的差异也是相当的大,所以为不同的浏览器开发不同的功能是行之有效的开发策略。

能力检测

又称特性检测,是最常用也最为人们所广泛接受的客户端检测形式。

最好的方法是检测某个属性是否为特定类型,而不是检测它是否存在,因为不排除有同名属性的存在。可以使用typeof操作符来完成这一目标。

DOM

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/836844
推荐阅读
相关标签
  

闽ICP备14008679号