第二章 javascript简介
- netscape创建LiveScript脚本语言。搭java顺风车就改为javascript。
- js三部分:ECMAScript,dom,bom
- script标签的defer和async属性只针对外部文件,一般不用,没有这两者的时候,就会按照在页面中出现的位置先后执行的。
- js文件放在html外部的优势:可维护性,可缓存,适应未来。
- 如果浏览器不支持javascript,那么可以用noscript标签来写出替代说明文字。
第三章 基本概念
3.1 语法
- 区分大小写
- 标识符
- 注释
单行(//) 多行(/ /) - 严格模式 use strict
javascript的一种不同的解析与执行模型。一些不确定的行为将得到处理以及某些不安全的操作会抛出错误。 - 语句
3.2 关键字和保留字
全部关键字:1
break,case,catct,continue,debugger,default,delete,do,else,finally,for,function,if,in,instanceof,new,return,switch,this,throw,try,typeof,var,void,while,with
3.4 数据类型
- 五个基本的数据类型:
string,number,boolean,null,undefined - 一个复杂类型:
object
3.4.1 typeof操作符
1 | var huang = "hzhaung"; |
3.4.2 Undefined类型
未申明的变量以及为初始化的变量都是undefined。
3.4.3 null类型
null值表示一个空的对象指针。1
null == undefined 返回true
注意的是一般设置变量为null是为了用这个变量来保存对象,故而要写出var xx=null。
3.4.4 boolean类型
- false,””,0+NAN,null,undefined都是false。换句话说就是其他的都是true了。
- 区别大小写。
3.4.5 number类型
1 | var num1 = 070; //八进制 |
- NUMBER.MIN_VALUE或者NUMBER.MAX_VALUE超出就会自动转换成infinity,如果是负的就在前面加上-。
ifFinity( ):判断数值是否在有限数值范围内,true表示在,false表示不在。
NaN(not a number)
- 任意涉及NaN的操作都会返回NaN;
- NaN与任何值都不相等,包含NaN本身。
isNaN( ):接受参数后会将其转化成数值,不能转化的话就会返回true。1
2
3console.log(isNaN("huang")); //true
console.log(isNaN(true)); //false
console.log(isNaN(NaN)); //true
数值转换:
函数 | 作用对象 |
---|---|
Number( ) | 可用于任何数据类型 |
parseInt( ) | 针对字符串 |
parseFloat( ) | 针对字符串 |
Number的转换规则:
参数 | 结果 |
---|---|
boolean | false-0;true-1 |
number | 简单的传入换个返回 |
null | 0 |
undefined | NaN |
string | “123”-123;“1.1”-1.1;“0xf”-15;“”-0;其他-NaN) |
object | 调用valueOf( ),然后依照前面的规则 |
NaN | 调用toString( ),然后依照前面的规则 |
parseInt( )示例:1
2
3
4
5
6
7
8console.log(parseInt("1234blue")); //1234
console.log(parseInt("0xA")); //0
console.log(parseInt("22.5")); //22
var num1 = parseInt("10",2); //2
var num2 = parseInt("10",8); //8
var num2 = parseInt("10",10); //10
var num2 = parseInt("10",16); //16
3.4.6 String类型
- toString( )除null和undefined,因为他们没有这个方法。一般是不用参数的,可以加一个参数来表示数值的基数。
- String( )规则:null返回”null”,undefined返回“undefined”。(在不知道要转化的值是不是null或者undefined)
3.4.7 Object类型
一组数据和功能的集合。每个实例具有以下的属性和方法。对象是实例的基础。
属性 | 作用 |
---|---|
Constructor | 保存用于创建当前对象的函数,构造函数 |
hasOwnproperty(propertyName) | 检查给定的属性在当前的实例中是否存在。不是在实例的原型中。 |
isPrototypeof(object) | 检查对象是否是另一个对象的原型。 |
propertyIsEnumerable | 检查是否可以用for-in来枚举。 |
toLocaleString( ) | 返回对象的字符串表示。 |
toString( ) | 返回对象的字符串表示。 |
valueOf( ) | 返回对象的字符串,数值或布尔值表示,通常和上个方法的返回值相同。 |
3.5 操作符
在对对象使用操作符的时候,要调用valueOf( )或toString( )来获得可以操作的值。
3.5.1 一元操作符
一元操作符:++,–,+,-。前置和后置的区别:前置的时候语句也改变。
递增和递减操作符的规则如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var str = "1";
var str1 = "z";
var b = false;
var f = 1.1;
var o = {
valueOf: function(){
return -1;
}
};
console.log(++str); //2
console.log(++str1);//NaN
console.log(++b); //1
console.log(++f);//2.1
console.log(++o);//0
//如果是对象,则会先调用valueOf(),然后根据返回的值来确定加减之后的值。
3.5.2 位操作符
32位,最后一个位表示符号位,0位正1为负。 正数就是常见的,对于负数则是以补码的形式储存(绝对值,反码,加1)。
但是输出负数时候这些操作都是隐藏的,输出的时候就是常见负数在前面加符号的形式。1
2var num = -18;
console.log(num.toString(2));//"-10010"
对于非数值使用位操作符,则先自动调用Number( ),然后再应用位操作。
按位非( ~)
返回数值的反码:操作数的负数减1。1
2
3var num = 29;
var num1 = ~ num;
console.log(num1); //-26按位与
- 按位或
- 按位异或
- 左移(<<)
移出的空位就以0补充。 - 右移
3.5.3 布尔操作符
1.逻辑非
对象-false;空-true;非空-false;0-true;任意非0-false;null-true;NaN-true;undefined-true
2.逻辑与1
var result = true && false;
3.逻辑或
3.5.4 乘性操作符
乘法,除法和求模
如果有一个不是数值,则会先自动调用number()之后再返回结果。
……
3.5.5 加性操作符
加法特殊:
- 两个字符串:就拼接起来。
- 只有一个字符串,则将另一个转换成字符串,然后拼接。
- 有一个是对象,数值或者布尔值,则调用toString( ),然后再拼接。
- 对于undefined和null,则调用String( )取到”undefined”和”null”。
1 | console.log(5+"5"); //"55" |
减法特殊:
不是数值的调用Number( )就OK啦。
3.5.6 关系操作符
规则:如示例代码1
2
3console.log(5 > 4);//true
console.log("h" > "z") //false 转换为字符编码比较。
console.log(5<"2") //false "2"自动转化成2
3.5.7 相等操作符
数据类型 | 说明 |
---|---|
一个boolean | 先转化成数值然后再判断 |
string+number | string转化成数值先 |
对象+非对象 | 调用对象的valueOf( )方法,得到的值再比较 |
null+undefined | 相等 |
有NaN | false |
对象+对象 | 比较是不是同一个人对象 |
全等和不全等:
- ===:未经转化的情况下相等才会返回true
1
2null == undefined -----true
null === undefined -----false
3.5.8 条件操作符
1 | variable = boolean_expression ? true_value : false_value ; |
3.5.9 赋值操作符
3.5.10 逗号操作符
1 | var num1,num2,num3; |
3.6 语句
整理不常用的,if,for就整理啦。
3.6.2 do-while语句
只有在循环体重的代码执行之后,才会测试出口条件。即,循环体内的代码至少会执行一次。1
2
3
4var i = 0;
do{
i += 2;
}while(i < 10);
3.6.5 for-in 语句
一种准确的迭代语句,可以用来枚举对象的属性。
3.6.6 label语句
标签 labe:statement。这个标签可以通过break或者continue来引用,一般与for语句配合使用。
3.6.7 break和continue语句
- break会立即退出循环
- continue也会立即退出循环,但是退出后会从循环的顶部继续执行。
1 | var no = 0; |
结合label可以说明是退出的那个循环。
1 | var num = 0; |
3.6.8 with语句
将代码的作用域设置到一个特定的对象中。
1 | with(location){ |
3.7 函数
好处:封装,任意时刻调用。
ECMAScript中函数可以通过return语句返回值。执行return后就会停止。如果return语句不带返回的值,则会默认返回undefined。
注:eval和arguments不可以定义为变量。
函数的参数:
- 函数内部的参数是一个数组,arguments对象。 类似array,arguments[0]是合法的,而且也有length属性。故: 在定义函数的时候参数是非必须的,在调用的时候加上参数也不会出问题。这是区别其他语言的。
- arguments对象可以和命名的参数一起使用。但是两者的内存空间是不一样的。
3.7.2 没有重载
没有重载:由于js函数的特点,则是无法实现重载的,如果存在两个则后取。
第五章 引用类型
5.2 Array类型
js的数组可以保存任何类型的数据,也可以自动增长。1
2
3
4
5
6
7// 创建方法一:Array构造函数
var colors = new Array();
//创建长度是20
var colors = new Array(20);
// 创建方法二:数组字面两表示法。
var colors = ["red", "yellow", "white"];
数组不仅仅是可读的。如下:1
2
3var colors = ["red", "blue", "green"];
colors.length = 2;
alert(colors[2]); //undefined 第三项移除了
5.2.1检测数组
1 | if(value instanceof Array){ |
但是这种方法是有问题:
- 假定单一的全局执行环境,如果有多个框架,则就会有多个全局执行环境。从而存在多个不同版本的构造函数。
改进:1
```
if(Array.isArray(value)){
xxx;
}
5.2.1转换方法
转换方法 | 说明 |
---|---|
toLocaleString( ) | 类似toString( ) |
toString( ) | 会返回由数组中每个值得字符串形式凭借而成的一个以逗号分隔的字符串。 |
valueOf( ) | 返回的还是数值 |
eg:1
2
3var colors=["red","blcak","white"]
console.log(colors.valueOf());//["red", "blcak", "white"]
console.log(colors.toString());//red,blcak,white
join( ):方法讲数组按照()内的参数连接起来。1
2
3
4
5var colors=["red","blcak","white"]
var test_one = colors.valueOf().join();
var test_two = colors.valueOf().join("-");
console.log(test_one);//red,blcak,white
console.log(test_two);//red-blcak-white
5.2.3栈方法
数组可以表现的像栈一样,栈是一种可以限制插入和删除项的数据结构。后进先出。
方法 | 功能 |
---|---|
push( ) | 将参数里面的对象逐个添加到数组中,并返回数组修改后的长度值。 |
pop( ) | 与push的作用相反,但是返回值是数组的最后一项 |
1 | var colors=["red","blcak","white"] |
5.2.4 队列方法
队列的访问规则是“先进先出”,表现在队列的末端添加项,在队列的前端移除项。
方法 | 功能 |
---|---|
shift( ) | 移除数组中的第一项,并返回该项 |
unshift( ) | 与shift的作用相反,可以在数组的前端添加任意个项并返回数组的长度。 |
1 | //模拟队列的组合 |
5.2.5 重排序方法
方法 | 功能 |
---|---|
reverse( ) | 反转数组项的排序 |
sort( ) | 按照升序的方法排列数组项,会调用每个项的toString( )转型方法,比较的是字符串 |
1 | var values = [0, 1, 5, 10, 15]; |
可见以上的sort( )的方法不够完美。
sort( )可以接受一个比较函数作为参数来完善不足。1
2
3
4
5
6
7
function compare(value1, value2) {
return value1 - value2;
}
values.sort(compare);
console.log(values);//[0, 1, 5, 10, 15]
可见,完美解决,达到排序的效果。
5.2.6 操作方法
方法 | 功能 |
---|---|
concat( ) | 基于当前数组中的所有项创建一个新的数组(副本),然后将接受到的参数添加到这个副本的末尾。 |
slice( ) | 基于当前的数组中的一个或者多个创建一个新的数组 |
splice( ) | 向数组的中部插入项。 |
1 | // concat |
splice( )详解:
1.删除
可以删除任意数量的项,指定两个参数:要删除的第一项的位置和要删除的项数。会返回删除的项。返回的是一个数组。1
2
3
4var colors = ["red","green","blue","yellow","purple"];
var remove = colors.splice(0, 2);
console.log(colors);//["blue", "yellow", "purple"]
console.log(remove);//"red", "green"]
2.插入
可以添加任意数量的项,提供3个参数,起始位置,0和要插入的项。1
2
3var removed = colors.splice(1,0,"huang","zhuang");
console.log(colors);//["blue", "huang", "zhuang", "yellow", "purple"]
console.log(removed);//[ ]
3.替换
可以在指定的地方出入任意数量的项,同事删除任意数量的项。1
2
3var removed = colors.splice(1,1,"huang","zhuang");
console.log(colors);//["blue", "huang", "zhuang", "zhuang", "yellow", "purple"]
console.log(removed);//["huang"]
发现:
- splice( )始终会返回一个数组。
- 返回的数组包含从原始数组中删除的项,如果没有删除过,就会返回一个空数组。
5.2.7 位置方法
方法 | 功能 |
---|---|
indexOf( ) | 返回项在数组中的位置 |
lastIndexOf( ) | 同上 |
接受两个参数:第一个是要查找的项,第二个参数表示的是表示起点位置的索引。
5.2.8 迭代方法
方法 | 功能 |
---|---|
every( ) | 对数组的每一项运行给定函数,全部是true就会返回true |
filter( ) | 对数组的每一项运行给点函数,返回改函数会返回true的项组成的数组 |
foeEach( ) | 对数组的每一项运行给点函数,无返回值 |
map( ) | 对数组的每一项运行给点函数,返回每次调用函数的结果组成的数组 |
some( ) | 对数组的每一项运行给点函数,如果该函数给任一项返回true,则就会返回true |
1 | //filter |
5.2.9 缩小方法
方法 | 功能 |
---|---|
reduce( ) | 迭代数组的所有项,然后构建一个最终返回的值,从第一项开始 |
reduceRight( ) | 迭代数组的所有项,然后构建一个最终返回的值,从最后一项开始 |
作为参数的函数接受4个参数:前一个值,当前值,项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。1
2
3
4
5var values =[1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){
return prev+cur;
});
console.log(sum);//15
5.3 Date类型
#第六章 面向对象的程序设计
内容:
- 理解对象属性
- 理解并创建对象
- 理解继承
6.1 理解对象
6.1.1 属性类型
内部才有的特性,用[[…]]来表示的。
1.数据属性
属性 | 描述 |
---|---|
[[Configurable]] | 能否delete删除属性从而重新定义、能否修改属性的特性、能否把属性修改为访问器属性。 |
[[Enumerable]] | 能否通过for-in枚举。默认是true |
[[Eritable]] | 能否修改属性的值 |
[[Value]] | 包含这个属性的数据值 |
1 | <script> |
如上所示:创建一个对象,它的默认内部属性前三个都是true,然后[[value]]被设置成了一个特定的值HuangZhuang。对象创建name属性,[[Value]]被设置了,在对name属性值得任何时候的修改都会反映在这个位置。
修改特性的默认值,只能调用:1
Object.defineProperty(属性所在对象,"属性名字","一个描述符对象")
如果要修改,则:1
2
3
4
5
6
7
8var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "hz"
})
console.log(person.name); //hz
person.name= "hh";
console.log(person.name); //hz
有一点注意:就是当Configurable是false时,其他三个的特性是要受到限制的,而且设定Object.defineProperty后,这四个特性均是默认设定成false的。
2.访问器属性
访问器属性不包含数组,但是包含一对getter和setter函数。
属性 | 描述 |
---|---|
[[Configurable]] | 能否delete删除属性从而重新定义、能否修改属性的特性、能否把属性修改为访问器属性。 |
[[Enumerable]] | 能否通过for-in枚举。默认是true |
[[Get]] | 读取属性时调用的函数 |
[[Set]] | 写入属性时调用的函数 |
访问器属性不可以直接定义,必须通过Object.defineProperty( )来定义。
1 | var book = { |
下划线表示只能通过对象方法访问的属性。
6.1.2 定义多个属性
Object.defineProperties(第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应)
6.1.3 读取属性的特性
一个函数Object.getOwnPropertyDescriptor( )针对属性是数据类型还是访问器类型返回出内部属性的值。
1 | var a = Object.getOwnPropertyDescriptor(book, "year"); |
6.2 创建对象
Object构造函数或对象字面量都可以创建单个对象,但是这种方式是有明显的缺点的:使用同一个接口创建很多对象,会产生大量的额重复代码。
6.2.1 工厂模式
用函数来封装以特定的接口创建对象的细节。1
2
3
4
5
6
7
8
9
10
11
12function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
}
return o;
}
var o1 = createPerson("hz", 25, 'IT');
......
但是却没有解决对象识别的问题,即怎样知道一个对象的类型。
6.2.2 构造函数模式
特点:
- 没有显示的创建对象
- 直接将属性和方法赋值给this对象
- 没有return语句
1 | function Person(name, age, job){ |
构造函数始终都应该以一个大写字母开头,非构造函数才是用小写字母开头。
以上的例子中,要创建一个Person的新实例,要使用new操作符。经理四个步骤:
- 1创建一个新对象。
- 2将构造函数打的作用域赋值给新对象,this就会指向新对象。
- 3执行构造函数中的代码。
- 4返回新对象。
Person创建的实例,都有一个指向Person的Constructor属性。1
console.log(Person1.constructor == Person ); //true
构造函数与其他函数的唯一区别就是调用方式。new 操作符来调用函数就是构造函数,如果没有就和普通函数没有区别的。
1
2
3
4
5
6
7
8
9
10
11
12// 作为构造函数使用
var person = new Person("hz", 25, "IT");
person.sayName();//hz
//作为普通函数使用
Person("hz", 25, "IT");//添加到window对象
window.sayName();
// 在另一个对象中使用
var o = new Object();
Person.call(o, "hz", 25, "IT"); //在o对象的特殊作用域中调用,o就拥有了所有属性和方法。
o.sayName();
构造函数的问题就是每个方法都要在每个实例上重新创建一遍。就是说不同实例上的同名函数实际上是不一样的。如果把这些方法通过全局作用域的函数调出来可以解决,但是当需要很多方法时,封装性太差。解决方法:原型模式。1
2
3var Person1 = new Person("hz", 25, "IT");
var Person2 = new Person("hz", 25, "IT");
console.log(Person1.sayName == Person2.sayName); //false
1.理解原型对象
任何时候创建新函数,就会创建一个prototype属性,这个属性指向函数的原型对象。默认情况下,原型对象会自动获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针。
虽然没有办法访问到[[prototype]],但是通过isPrototypeOf( )方法可以确定对象之间是否存在这种关系。如果[[prototype]]指向调用isPrototypeOf( )方法的对象,就会返回true。
1 | console.log(Person.prototype.isPrototypeOf(person1)); //true |
而ECMAScript5新增加的一个方法Object.getPrototypeOf( )返回的对象实际就是这个对象的原型。Object.getPrototypeOf( )很方便的取得一个对象的原型。1
console.log(Object.getPrototypeOf(person1) == Person.prototype); //true
几点说明:
- 当代码读到属性时候,就会进行一次搜索,先从实例开始,如果没有搜到,则就会继续搜索指针指向的原型。
- 实例中重新定义新的属性和方法不会改变原型中的属性和方法,但是在调用该实例的时候会屏蔽原型的。
- delete删除操作符可以完全删除实例属性。从而可以重新访问原型中的属性。
- hasOwnPrototype( )检测一个属性是否存在实例中,还是存在原型中。这个方法是继承过来的,只有在实例设定属性或者方法的时候才会返回true;
2.原型与in操作符
- 单独使用in的时候,会在对象能够访问给定属性时返回true,实例和原型均可。
1
console.log("name" in person1); //true
通过hasOwnPrototype( )和in结合就可以封装一个检测属性是实例还是原型中的函数:1
2
3function hasPrototypeProperty(object, name){
return !Object.hasOwnProperty(name) && (name in object);
}
原理:只要in操作符返回true而且.hasOwnProperty( )返回false就可以判断属性是存在原型中的。
- for-in循环,返回的是所有能够通过对象访问的,可枚举的属性。包括在实例中的属性以及原型中的属性。屏蔽了原型中不可枚举属性的实例属性也会返回。
Object.keys()方法可以返回所有可枚举的实例属性。参数如果是xxx.prototype则返回原型课枚举的属性。如果对于实例调用则只会返回实例的属性和方法,不会返回原型的。
如果要得到所有属性和方法,不管是不是可枚举,则使用getOwnPrototypeNames()1
2
3
4var keys = Object.keys(Person.prototype);
console.log(keys);//["name", "age", "job", "sayName"];
var key = Object.getOwnPropertyNames(Person.prototype);
console.log(key);//["constructor", "name", "age", "job", "sayName"];
3.更简单的原型方法
为减少不必要的代码书写,可以使用如下的方法:1
2
3
4
5
6
7
8
9
10function Person(){}
Person.prototype = {
constructor : Person,//一般没有,是为了重新设置constructor才用设置,但是会带来问题。
name : "hzhuang",
age : 25,
job : "IT",
sayName : function(){
console.log(this.name);
}
}
如上,Person.prototype设置成等于一个以对象字符量形式创建的新对象。但是值得注意的是此时constructor不再指向Person。此时的语法是完全重写了prototype对象,所以constructor属性就指向l构造函数Object,不再是Person了。如下:1
2
3var friend = new Person();
console.log(friend.constructor == Person);//false
console.log(friend.constructor == Object);//true
如果想重新设定回去(让friend.constructor == Person是true),那么就要在这个对象字面量里面添加一组condtructor的键值对。见3开始的例子。但是会带来问题,会让constructor属性变成可枚举的。
在兼容ECMAScript5的引擎中,可以用Object.definePeoperty来解决这个问题。1
2
3
4Object.defineProperty(Person.prototype, "constructor", {
enumerable : false,
value : Person
})
4.原型的动态性
对原型对象所做的任何修改都可以立即在实例上反应出来。即使是先创建实例然后修改原型。
但是如果是重写原型,情况就大不一样了。调用构造函数时会为实例添加一个执行最初原型的[[prototype]]指针,而把原型修改成另一个对象就等于切断了构造函数与最初原型的联系。实例中的指针只执行原型,不指向构造函数。1
2
3
4
5
6
7
8
9
10
11
12function Person(){}
var friend = new Person();
Person.prototype = {
constructor : Person,
name : "hzhuang",
age : 25,
job : "IT",
sayName : function(){
console.log(this.name);
}
}
friend.sayName();//Uncaught TypeError: friend.sayName is not a function
5.原生对象的原型
不建议修改原生对象的原型。
6.原型对象的问题
- 省略了为构造函数传递初始化参数这一环节,结果所有实例在默认的情况下都将取得相同的属性值。
- 最大的问题是有共享的本性造成的。
原型中的所有属性都可以被很多实例共享,但是对于包含引用类型的属性来说们就会出现很明显的问题。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function Person(){}
Person.prototype = {
constructor : Person,
name : "hzhuang",
age : 25,
job : "IT",
friends : ["a", "b"],
sayName : function(){
console.log(this.name);
}
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("c");
console.log(person2.friends);//["a", "b", "c"]
问题好明显,我只是修改了实例1的friends,但是却在所有的实例中反应出来了。基于此,单独的原型模式一般是不常用的。
6.2.4 组合使用构造函数模式和原型模式
- 构造函数用于定于实例属性;
- 原型模式用于定于方法和共享的属性。
重写之前有问题的例子:1
2
3
4
5
6
7
8
9
10
11
12function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["a", "b"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
console.log(this.name);
}
}
可见这种混合模式的优点:
- 每个实例都有自己的实例属性的副本,同时又有共享的方法。
- 最大限度的节省了内存
- 还支持构造函数传递参数
6.2.5 动态原型模式
把所有的信息封装在构造函数中,而通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。就是说可以通过检测某个应该存在的方法是否有效,来决定是否需要初始化原型。1
2
3
4
5
6
7
8
9
10
11
12
13function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["a", "b"];
if(typeof this.sayName != "function"){
Person.prototype.sayName = function(){
console.log(this.name);
}
}
}
var friend = new Person("hzhuang", 25, "IT");
friend.sayName();
if( )部分只会在初次调用函数的时候执行,之后原型就完成初始化。此时原型的修改都会反映在实例中。
6.2.6 寄生构造函数模式
创建一个函数,这个函数的作用就是封装创建对象的代码,然后再返回新创建的对象。和工厂模式一样,区别只是这里是在一个函数里面并返回的这个对象。
6.2.7 稳妥构造函数模式
- 没有公共属性。
- 也不用this对象。
1 | function Person(name, age, job){ |
除了调用sayname( )方法外,没有其他方法可以访问到传入到构造函数中的原始数据,保证了一种安全性。
6.3 继承
js没有接口继承,只支持实现继承。
6.3.1 原型链
原型链是实现继承的主要方法。
基本思路:利用原型让一个引用类型继承另一个引用类型的属性和方法。
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而每个实例都包含一个指向原型函数对象的内部指针。如果将原型对象等于另一个类型的实例。则就会形成一个链。
1 | function SuperType(){ |
几点说明:
- instance.constructor指向SuperType,因为SubType的原型重写了。
- instance指向SubType的原型
- SubType的原型又指向SuperType的原型
1.别忘记默认的原型
所有的函数的默认原型都是Object的实例,因此默认原型都会包含一个内部的指针,指向Object.prototype。这就是为什么函数可以继承toString( )等默认方法的根本原因。
2.确定原型和和实例的关系
两种方式:
方式 | 描述 |
---|---|
instanceof | 只要用这个操作符来测试实例与原型链中出现的构造函数,就会返回true。 |
isPrototypeOf( ) | 只要是原型链中出现过的原型,都可以说是该原生链所派生的实例的原型。 |
3.谨慎的定义方法
给原型添加方法的代码一定要放在替换原型的语句之后。
必须在用要被继承的构造函数的实例替换当前的原型之后,再定义方法。
通过原型链实现继承的时候,不能使用对象字面量创建原型方法。因为这样会重写原型链。1
2
3
4
5
6
7
8
9
10
11
12
13// 继承SuperType()
SubType.prototype = new SuperType();
SubType.prototype = {
getSubValue : function(){
return this.subproperty;
},
someOhterMethod : function(){
return false;
}
}
var instance = new SubType();
console.log(instance.getSuperValue()); //error
刚刚把SuperType的实例赋值给原型,接着又将原型替换成一个对象字面量而导致的问题。由于现在的原型包含的是一个Object的实例,而非SuperType的实例,因此我们设想的原型链已经被切断了。
4.原型链的问题
最大的问题是引用类型值的原型。还是共享问题。引用类型。
基于此,很少单独使用原型链。
6.3.2 借用构造函数
解决单独原型链中引用类型带来的问题,借用构造函数的技术。
基本思想:在子类型构造函数的内部调用超类型构造函数。函数是特定环境中执行代码的对象,可以使用apply( )和call( )方法在新创建的对象上执行构造函数。
1 | function SuperType(){ |
在新SubType对象上执行SuperType( )函数中定义的所有对象初始化代码。
1.传递参数
2.借用构造函数的问题
方法在构造函数中定义,所以就无法复用。
6.3.3 组合继承
将原型链和借用构造函数的技术组合在一起。
思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样在原型上定义的方法实现了函数的复用,又可以保证每个实例有它自己的属性。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
28function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
}
function SubType(name, age){
//继承SuperType
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
console.log(this.age);
}
var instance1 = new SubType("hzhuang", 25);
instance1.colors.push("blcak");
console.log(instance1.colors);//["red", "blue", "green", "blcak"]
instance1.sayName();//hzhuang
instance1.sayAge();//25
var instance2 = new SubType("hz", 26);
instance2.sayName();//hz
instance2.sayAge();//26
6.3.4 原型式继承
没有严格意义上的构造函数。借助原型可以基于已有的对象创建新对象,同时还不比创建自定义类型。1
2
3
4
5
6function object(o){
function F(){
F.prototype = o;
return new F();
}
}
在object函数内部,先创建了yige 临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类的一个新实例。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function object(o){
function F(){}
F.prototype = o;
return new F();
};
var person = {
name : "hzhuang",
friends : ["a", "b", "c"]
};
var anotherPerson = object(person);
anotherPerson.name = "hz";
anotherPerson.friends.push("d");
var yetanotherPerson = object(person);
yetanotherPerson.name = "hh";
yetanotherPerson.friends.push("e");
console.log(person.friends);//["a", "b", "c", "d", "e"]
在上面的实例中,可以用Object.create( )来代替自定义的object函数。但是可以达到相同的效果。
6.3.5 寄生式继承
思路与寄生构造函数和工厂模式类似,即创建一个仅仅用于封装继承过程的函数,改函数内部已某种方式增强对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function object(o){
function F(){}
F.prototype = o;
return new F();
};
var person = {
name : "hzhuang",
friends : ["a", "b", "c"]
};
function createAnother(original){
var clone = object(original);
clone.sayHi = function(){
return console.log("hi");
};
return clone;
}
var anotherPerson = createAnother(person);
anotherPerson.sayHi();
6.3.6 寄生组合式继承
基本思路:不必为了指定子类型的原型而调用超类型的构造函数,所需要的无非是超类型原型的一个副本人而已。本质上使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。1
2
3
4
5function inheritPrototype(subType, superType){
var prototype = object(superType.prototype);//创建对象,创建超类型原型的一个副本。
prototype.constructor = subType;//增强对象
subType.prototype = prototype;//指定对象
}
第七章 函数表达式
- 函数表达式的特征
- 使用函数实现递归
- 使用闭包定义私有变量
函数声明可以先调用,然后再声明,不会跑出错误。但是如果你使用函数表达式的方法,则必须先创建函数啊,然后调用。
函数表达式:var huang=function(a,b){};这种情况叫做匿名函数,因为function后面没有标识符。在使用前必须先赋值。
7.1 递归
1 | function factorial(num){ |
arguments.callee是一个指向正在执行函数的指针。
7.2 闭包
闭包:有权访问另一个函数作用域中的变量的函数。最常见的方式就是在一个函数内部创建另一个函数。
函数被调用的时候,会创建一个执行环境和相应的作用域链,然后用arguments或者其他命名参数来初始化函数的活动对象。
- 每一个执行环境都有有一个变量对象。全局变量对象始终存在,而局部的活动对象(本地活动对象)只在函数执行的过程中存在。
- 作用域链的本质是一个指向变量对象的指针列表,它只是引用。
在另一个函数内部定义的函数会将包含函数(外部函数)的活动对象添加到它的作用域中。
函数调用完成后不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。
###7.2.1 闭包和变量
闭包中保存的是整个变量对象,所以闭包只能取得包含函数中任何变量的最后一个值。1
2
3
4
5
6
7
8
9
10
11
12
13function createFunctions(){
var result = new Array();
for(var i = 0; i < 10; i++){
result[i] = function(){
return i;
}
}
return result;
}
var funcs = createFunctions();
for(var i = 0; i < 10; i++){
document.write(funcs[i]() + "<br />");
}
输出的全是10,值得注意的是:funcs[i]输出的是一个函数,则在后面加上()就会执行返回i的值。
想到达预期的效果,则如下:1
2
3
4
5
6
7
8
9
10
11function createFunctions(){
var result = new Array();
for(var i = 0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i)
}
return result;
}
###7.2.2 关于this对象
匿名函数的执行环境具有全局性,因此一般this指向window。但是有时候由于闭包的方式不同,这一点可能不同。
闭包中,内部函数是在先搜索自己内部的活动对象那个,如果找不到的话就会继续搜索外部包含它的函数。
如果将外部函数作用域中的this对象保存在一个闭包能够访问的变量里。1
2
3
4
5
6function(){
var that = this;
return function(){
return that.xxx;
}
}
###7.2.3 内存泄露
内存泄露:闭包会引用包含函数的整个活动对象。
这种方式可以有效的回收内存。
##7.3 模仿块级作用域
js没有块级作用域的概念,所以对于for里面的声明的变量在函数内部也是可以读取的。对后续的声明无视,但是初始化值会执行。匿名函数模仿块级作用域(私有作用域)。
区别:1
2
3(function(args){ //这是一个函数表达式,后面加上()表示立即调用。
xxx; //块级作用域。
})()
如果没有保住function的括号,则就是函数声明,函数声明后面是不能加括号的。
##7.4 私有作用域
没有私有成员的概念,但是有私有变量的概念。(函数中定义的都是私有变量)
访问私有变量的公有方法:就是在函数内部调用一个特殊的方法,利用闭包的作用链来访问这些私有变量和私有方法。构造函数实现,对每个实例都重新创建。
###7.4.1 静态私有变量
在私有作用域中定义私有变量和函数,也可以创建特权方法。
在匿名函数里面,通过不声明函数的方法使这个方法成为全局的,所有就可以在私有作用域之外也可以访问到私有变量和方法。
区别:私有变量和方法是实例共享的。实例的参数变化会影响所有的实例。
###7.4.2 模块模式
在定义私有变量和函数后,return一个对象字面量。这个对象字面量包含可以公开的属性和方法。
第八章 BOM
主要内容
- 理解window对象
- 控制窗口、框架和弹出窗口
- location对象中的页面信息
- navigator对象了解浏览器
8.1 window对象
BOM的核心对象是window,它表示浏览器的一个实例。
- window对象既是js访问浏览器窗口的一个接口。
- window也是ECMAScript规定的Global对象。网页中定义的对象、函数等都以window作为Global对象。
8.1.1 全局作用域
由于window的特性,所以在在全局作用域中声明的变量、函数都会变成window对象的属性和方法。1
2
3
4
5
6
7var age = 29;
function sayAge(){
console.log(this.age);
};
console.log(window.age);
sayAge();
window.sayAge();
定义全局变量与在window对象上定义属性还有一点差别:全局变量不能通过delete操作符删除,但是在window对象上定义的属性可以使用这个操作符。
8.1.2 窗口关系及框架
页面中如果有框架,则每个框架都有自己的window对象。保存在frames集合中。每个window都有各自的name属性。
不常用,忽略。
8.1.3 窗口位置
screenLeft和screenTop属性,分别用于表示窗口相对于屏幕左边和上面的位置。特别的是:在FireFox中,是screenX和screenY两个属性来完成相同的功能。1
2var leftPos = (typeof window.screenLeft == "number")?window.screenLeft : window.screenX;
var topPos = (typeof window.screenTop == "number")?window.screenTop : window.screenY;
关于方法moveTo( )和moveBy( )浏览器一般是禁用的。
8.1.4 窗口大小
关于窗口大小属性的四个:outerWidth/innerWidth/outerHeight/innerOuter在各浏览器中的渲染差别。
- IE9+、Safari和FireFox:outerWidth/outerHeight返回浏览器窗口本身的尺寸。
- Opera: outerWidth/outerHeight表示页面视图容器的大小。
- Chrome:innerWidth/innerHeight一般表示页面中视图区的大小(减去边框宽度)。而chrome中这对属性的值和outerWidth/outerHeight返回的值大小是一样的,都表示视口的大小。
跨浏览器确定浏览器的窗口无法实现,但是可以获取到页面视口的大小。1
2
3
4
5
6
7
8
9
10
11
12var pageWidth = window.innerWidth;
var pageHeight = window.innerHeight;
if(typeof pageWidth != "number"){
if(document.compatMode == "CSS!compatMode"){ //检查是否处于标准模式
pageWidth =document.documentElement.clientWidth;
pageHeight = document.documentElement.clientHeight;
} else {
pageWidth = document.body.clientWidth;
pageHeight = document.body.clientHeight;
}
}
移动设备中,IE的浏览器的视口大小的值是可见视口的值,而其他浏览器的值就是布局视口的值。
同样有resizeTo( )和resizeBy( ),但是一般浏览器禁止。
8.1.5 导航和打开窗口
window.open( )可以打开导航到一个特定的URL,也可以打开一个新的浏览器窗口。
window.open( )接受四个参数:要加载的URL、窗口目标、一个特性字符串以及新页面是否取代浏览器历史记录中当前加载页面的布尔值。
- 第一个参数:要加载的URL
- 第二个参数:窗口目标。
- 已存在的窗口或者框架属性。
- 特殊的窗口名称:_self、_parent、_top或_blank。
- 第三个参数:在第二个参数并不是一个存在的窗口或者框架的时候。则会根据第三个设置的特性值新建窗口或者框架。
特性值 | 说明 |
---|---|
fullscreen,取值yes或no | 仅限IE |
height | |
left | 新窗口的左坐标 |
location取值yes或no | 是否在新窗口中显示地址栏 |
menubar取值yes或no | 是否在新窗口中显示菜单栏 |
resizeable | 是否可以通过拖动浏览器窗口的边框改变其大小 |
scrollbars | 是否允许有滚动 |
status | 是否在新窗口中显示状态栏 |
toolbar | 是否在新窗口中显示工具栏 |
top | |
width | 无 |
以上的设置值是yes或者no的默认值都是no。1
var hzhuang = window.open("http://localhost:4000/", "hzhaung", "height=400,width:400,resizeable=no");
window.close( )方法用于关闭新打开的窗口。浏览器主窗口是不可以使用这个方法的,凡是弹出的窗口却可以。
新创建的window对象有一个opera属性,保存着打开它的原始窗口对象。
可以通过设置这些特性字符串来处理网页上肆无忌惮的广告问题。
弹出窗口屏蔽程序
大多数浏览器都内置屏蔽弹出窗口。1
2
3
4
5
6
7
8
9
10
11
12
13
14var blocked = false;
try {
var wroxWin = window.open("http://www.wrox.com", "_blank");
if (wroxWin == null){
blocked = true;
}
} catch (ex){
blocked = true;
}
if (blocked){
alert("The popup was blocked!");
}
8.1.6 间歇调用和超时调用
windo对象的setTimeout( )方法。
这个方法会返回一个数值ID,表示超时调用。用clearTimeout(set方法返回的值)来取消。1
2var timeoutID = setTimeout(function(){console.log("hzhuang");}, 1000);
console.log(timeoutID);//1
window对象的setInterval( )实现间歇调用。
8.1.7 系统对话框
三种:alert() + confirm( ) + prompt( )
- 与显示的网页没有关系。
- 外观由操作系统决定,并非css。
- 显示的时候代码是停止执行的。
8.2 location对象
location对象即是window对象的属性,也是document对象的属性。
- 保存当前文档的信息
- 将URL解析成独立的片段
以“http://www.wrox.com:80#contents”为例
属性名 | 例子 | 描述 |
---|---|---|
hash | “#contents” | URL的hash,就是#号后面的字符串 |
host | “www.wrox.com”80” | 返回服务器名称和端口号 |
hostname | … | 返回服务器名 |
href | “http://www.wrox.com” | 返回当前加载页面的完整URL |
pathname | /…/ | url的路径名 |
port | “80” | 端口 |
protocal | “http” | 协议 |
search | ”?q=javascript“ | 返回URL中的查询字符串,以问号开头 |
8.2.1 查询字符串参数
解决location.search只能返回问号后面全部字符串而不精确的问题。
构造一个函数,解析查询字符串,然后返回包含所有参数的一个对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21function getQueryStringArgs(){
var qs = (location.search.length > 0 ? location.search.substring(1) : ""),
args = {},//保存数据的对象
//获取每一项
items = qs.length ? qs.split("&") : [],
item = null,
name = null,
value = null,
i = 0,
len = items.length;
for(i = 0; i<len ;i++){
item = items[i].split("=");
name = decodeURLComponent(item[0]);
value = decodeURLComponent(item[1]);
if(name.length){
args[name]= value;
}
}
return args;
}
###8.2.2 位置操作
location对象可以用很多方式改变浏览器的位置。常用的是assign( )。1
location.assign("http://huangzhuang.github.io/")
以上代码可以立即打开我的博客,并在浏览器中的历史记录中生成一条记录。
window.location和location.href设置成URL时,也会调用assign的方法,达到相同的效果。
同时设置location对象的其他属性也可以改变URL。
为了禁止在浏览器器中生成历史记录,可以采用replace( )的方法。
最后一个方法是reload( ),重新加载当前页面。1
location.reload(true) //带true表示的是从服务器中重新加载的。
8.3 navigator对象
关于属性,不常用,详见P210.
8.3.1 检测插件
对于非IE浏览器而言,使用plugins数组来实现插件检测。
plugins数组有以下的属性:
- name 插件的名字。
- description插件的描述。
- filename插件的文件名。
- length插件所处理的MIME类型数量。
1 | // 检测是否有xx插件 |
要再IE浏览器中检查插件,则使用唯一的专有的ActiveXObjext类型。1
2
3
4
5
6
7
8
9//plugin detection for IE
function hasIEPlugin(name){
try {
new ActiveXObject(name);
return true;
} catch (ex){
return false;
}
}
###8.3.2 注册处理程序
##8.4 screen对象
screen对象基本上只用来表明客户端的能力。
属性详见P214
8.5history对象
history是window对象的属性。借由用户访问过的页面列表,可以实现在不知道实际URL的情况下实现后退和前进。1
2
3history.go(num/url);
history.back();
history,forward();
#第十章 DOM
- 理解包含不同层次节点的DOM
- 使用不同的节点类型
- 克服浏览器兼容性问题以及各种陷阱
10.1 节点层次
所有页面标记则表现为一个以特定节点为根节点的树形结构。
10.1.1 Node类型
每个节点有nodeType属性。
为了兼容所有浏览器,最好是将nodeType的值与1-12之间的数值比较。1
if(somenode.nodeType = =1){alert("该节点是一个元素节点")}
####1.nodeName和nodeValue属性
nodeName和nodeValue属性是节点的具体信息,使用前检测下节点的类型。
元素节点的nodeName是标签名,而它的nodeValues是null。
####2. 节点关系
每个节点都有一个childNodes属性,保存着nodeList对象(一个类数组,用于保存一组有序的节点)。
nodeList对象有length属性,且并不是Array的实例,而是会随时变化的,随DOM的结构变化自动变化。
通过方括号或者item()方访问nodeList中的节点。1
2
3
4var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item[0];
//将nodeLists转化成数组。
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes, 0);
关于Array.prototype.slice.call()可以将arguments对象转成数组,参考解释。
大致是先调用toArray()方法,然后再执行slice()方法。
关于将nodeList对象转换成数组,兼容IE8及以下的方法是:1
2
3
4
5
6
7
8
9
10
11
12
13
14function convertToArray(nodes){
var array = [];
try{
//针对非IE8及以下浏览器
array = Array.prototype.slice.call(nodes, 0);
}
catch(ex){
array = new Array();
for(var i=0, len= nodes.length; i< len; i++){
array[i].push(nodes[i]);
}
}
return array;
}
每个节点有一个parentNode属性,指向文档树中的父节点。
比如someNode.childNodes中的每个节点都有同样的父节点someNode。而且他们之间是同胞节点(previousSibling 和 nextSbling)。
firestChild和lastChild属性的意义就很明显了。1
2someNode.firstChild == someNode.childNodes[0];
someNode,lastCHild == someNode.childNodes[someNode.childNodes.length - 1];
hasChildNodes()方法:节点包含一个或者多个子节点的情况下返回true
每个节点还有一个ownerDocument属性,指向文档的文档节点。不可以跨几个。
####3.操作节点
|方法|描述|
|:—|:—|
|appendChild( )|用于向childNodes列表的末尾添加一个节点。这个方法会返回新增的节点。|
|insertBefore()|(插入的节点,作为参考的节点 )|
|replaceChild()|(插入的节点,要替换的节点)|
|removeChild|(移除的节点)|1
2var huang= someNode.appendChild(newNode);
alert (huang == newNode); //返回true;
如果要传入的节点已经是DOM的一部分,则结果就是将该节点从原来的位置转移到新的位置。(实现转移!)
四种操作方法都是摸个节点的子节点,如果是不支持子节点的节点就会报错的。比如文本节点没有子节点。
两个所有节点都有的方法:
方法 | 描述 |
---|---|
cloneNode(true) | true表示深复制,就是复制节点及其子节点树。复制后孤立。不会复制js属性。 |
normalize() | 删除后代中的空文本节点,然后把相邻的文本节点合并。 |
###10.1.2 Document类型(9)
nodeType == 9
document对象是HTMLDocument的一个实例,表示整个HTML页面。且是window对象的一个属性。
####1.文档的子节点
内置两个访问子节点的快捷方式,所有的浏览器均支持这两个。
- documentElement指向html标签,这个更加方便快捷。
- 通过childNodes列表访问。
一下三个是等价的。1
2
3var html =document.documentElement;
var html1 = document.childNodes[0];
var html2 = doxument.firstChild;
document对象有body属性1
var body = document.body;
documen对象有doctype属性,各浏览器支持差异,使用不多。1
var dovtype = document.doctype;
对于出现在html之外的注释,各个浏览器解析也存在差异。也就是document的childNodes不同。
- IE8及以前,safari3.1及以后。为第一个注释创建节点。其他的就忽略。
- IE9及以上,就是正常的情况,所有的注释都是创建了节点。
- Firefox和safari以前,完全忽略注释的存在。
####2.文档信息
作为HTMLDocument的实例,有一些特别的属性。
属性 | 描述 |
---|---|
title | document.title |
URL | 包含页面完整的URL |
domain | 只包含域名 |
referrer | 保存着链接到当前页面的那个页面的URL |
后面的三个属性是与网页的请求有关,存在请求的http头部。且只有domian是可以设置的,但是不可以任意设置。而且可以把子域设置成域而不能把域设置成子域(不能loose到tight)。
理解是这样的:
由于跨域,不同的子域的页面是无法通过js通信。可是将两个子域的domian设置为相同的值就可以通信了。
####3.查找元素,document对象的几个方法。
- document.getElementById():很常见,但是要注意一点就是在IE8及以下中会返回name值等于这个id名的表单元素。
document.getElementsByTagName:返回一个HTMLCollection对象,这个对象与nodeList对象类似,也可以使用方括号或者item()来读取。
这个HTMLCollection对象还有一个namedItem方法,来获取这个集合中name特性的项。则可以知道:HTMLCollection而言,可以传入数值或者字符串,前者后台会调用item(),后者则调用namedItem()。1
2var img = doceument.getElemmentdbyTagName("img");
var myImg = img.namedItem("hzhuang");//可以获取到name是hzhuang的那张图片。document.getElementByName()。
####4.特殊集合
这些特殊的集合都是HTMLCollection对象。
集合 | 描述 |
---|---|
document.anchors | 包含文档中所有带name特性的a标签 |
document.forms | 包含文档中所有的form元素 |
document.images | 包含文档中所有img标签 |
document.links | 包含文档中所有带href属性的a元素 |
#####5.DOM一致性检测
检测浏览器实现了DOM的哪些部分。
document.implementation属性就是来实现这个功能的。
它有一个方法hasFeature().如果浏览器支持给定名称和版本的功能就会返回true。1
2var hasXmlDom = document.implementation.hasFeature("XML", "1.0");
console.log(hasXmlDom); //true
####6.文档写入
将输出流写出到网页中。
- write:参数里面包含script标签时闭合的/要转义。
- writeIn会自动在后面加上(/n)。
###10.1.3 Element类型(1)
nodeType == 1
访问元素的标签名:
- nodeName属性
- tagName属性
在HTML返回的都是大写的。所以采用以下的方式进行比较返回的是否是哪个。1
if(element.nodeName.toLowerCase() == "div");
####1.html元素
每个html元素都有以下的特性:
- id
- title
- dir:修改就会立即反映在页面中。
- lang
- className:改变值可以直接关联新的样式。
####2.取得特性
操作特性的方法:
getAttribute() + setAttribute() + removeAttrvbute()
两类特殊的特性:
- style:getAttribute()返回的是style里面的文本。而通过属性来访问则是一个对象。
- onclick:getAttribute()返回相应代码的字符串。而访问onclick属性时候返回的是一个js函数。
1 | <div id="huang" onclick="huang()"></div> |
所以一般不适用getAttribute()这个方法,而是在访问自定义属性的时候用到。
####3.设置属性
setAttribute()
IE7及以下对于class和style的设置没有效果。
IE6不支持removeAttribute()
####4.attributes属性
atributes属性中包含一个NamedNodeMap,与nodeList类似。
有以下的方法:
- getNamedItem(name):返回nodeName等于name的节点。
- removeNamedItem(name);
- setNamedItem(name);
- item(pos):返回位于数字跑pos位置处的节点。
attributes属性包含一系列节点,每个节点的nodeName就是特性的名称。每个节点的nodeValue就是特性的值。
1 | var id = element.attributes.getNamedItem("id").nodeValue; |
attributes属性一般用的不多,但是在遍历元素的特性的时候是很有用的。然后以name = value的形式输出。1
2
3
4
5
6
7
8
9
10
11
12
13
14<div id = "hzhuang" name = "hzhuang" dir = "ltr" class = "hzhuang" title="hzhuang"></div>
function outputAttribute(element){
element = element.attributes;
var pairs = [], attrName, attrValue, i , len=element.length;
for(i=0; i< len; i++){
attrName = element[i].nodeName;
attrValue = element[i].nodeValue;
pairs.push(attrName + "=\'" + attrValue + "\'");
}
return pairs.join(" ");
}
var hzhuang = document.getElementById("hzhuang");
var output = outputAttribute(hzhuang);
console.log(output);//id='hzhuang' name='hzhuang' dir='ltr' class='hzhuang' title='hzhuang'
####5.创建元素
cteateElement()来实现,然后使用节点添加的方法加到DOM中。
几个兼容性问题及解决:
兼容性:
- 不能设置动态创建的iframe的name特性。
- 不同用reset()方法充值动态创建的input元素。
- 动态创建type是reset的button元素重设不了表单。
- 动态创建的一批name相同的单选按钮彼此之间没有联系。
解决方法:
检测浏览器(只有IE7及以下支持这种写法)1
2
3if(client.browser.ie && client.browser.ie <= 7){
xxx//使用参数是完整的元素标签。
}
####6.元素的子节点
元素的childNodes属性包含所有的子节点:元素,文本节点,注释或者处理指令。
解析的差异:1
2
3
4
5<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
- IE解析是ul.childNodes中只有三个子节点。
- 其他浏览器中解析出7个。(加上四个文本节点:空白符)
所以采用以下的方法来实现三个1
2
3
4
5for(var i=0, len=element.childNodes.length; i< len; i++){
if(element.childNodes[i].nodeType == 1){
xxx;
}
}
元素也有getElementsByTagName()方法。
###10.1.4 Text类型(3)
nodeType == 3
nodeValue或者data属性可以访问到文本节点包含的文本。
方法 | 作用 |
---|---|
appendData(text) | 将text添加到节点的末尾 |
deleteData(offset, count) | 从offset指定的位置删除count个字符 |
insertData(offset, text) | 从offset指定的位置添加text |
replaceData(offset,count, text) | 从offset指定的位置用text替换count个字符 |
splitText(offset) | 指定的位置将当前文本节点分成两个文本节点。 |
substringData(offset, count) | 提取从指定位置offset到offset+count位置处的字符串。 |
1 | function changeText(){ |
####1.创建文本节点
document.createTextNode(),然后使用添加节点的方式将它放进DOM。
如果添加两个相邻的文本节点,会将两个直接连接起来。且没有空格。但是还是存在两个文本节点。则使用规范化来解决这个问题。
####2.规范化文本节点
normalize()在包含一个或者几个文本节点的父节点上调用这个方法,则可以将所有的文本节点合并成一个。也就是说此时element.childNodes.length是等于1的。
####3.分割文本节点
splitText()方法的作用域normalize()的作用相反。
10.1.5 Comment类型(8)
nodeType == 8
类似Text类型
10.1.7 DocumentType类型(x)
不常用。
10.1.8 DocumentFragment类型(11)
nodeType == 11
不可以直接添加在文档中,但是可以作为仓库来使用。
创建方法:1
var fragment = document.createDocumentFragment();
看一个例子就明白了:1
2
3
4
5
6
7
8
9
10
11<ul id="myList"></ul>
var fragment = document.createDocumentFragment();
var ul = document.getElementById("myList");
var li = null;
for(var i = 0; i< 3; i++){
li = document.createElement("li");
li.appendChild(document.createTextNode("Item " + (i+1)));
fragment.appendChild(li);
}
ul.appendChild(fragment);
11.2 DOM操作技术
11.2.1 动态脚本
页面加载完的时候不存在,在将来的某一个时刻修改DOM动态添加的脚本。
- 插入外部文件。
- 直接插入js代码。
1 | var script = documet.createElement("script"); |
第二种方式:1
2
3
4
5<script>
function sayHi(){
alert(:hi);
}
</script>
要动态创建以上的那个js,则要考虑到兼容性的问题
- document.createTextNode(“function sayHi(){alert(‘hi’)}”除了IE浏览器不支持外,其他的均支持。 但是可以指定script标签的text属性来指定代码。
- 但是sarafi3.0之前的不支持text的属性。
所以,以下就是封装好的函数,兼容性解决的。1
2
3
4
5
6
7
8
9
10function loadScriptString(code){
var script = document.createElement("script");
script.type = "text/javascript";
try{
script.appendChild(document.createTextNode(code));
} catch (ex){
script.appendChild(script.text = code);
}
document.body.appendChild(script);
}
10.2.2 动态样式
也是两种方式
与script类似,但是区别的是link是放在head中的。封装好的函数如下。1
2
3
4
5
6
7
8
9
10
11function loadStyleString(css){
var style = document.createElement("style");
style.type = "text/css";
try{
style.appendChild(document.createTextNode(css));
} catch (ex){
style.styleSheet.cssText = css;
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);
}
10.2.3 操作表格
忽略
10.2.4 使用NodeList
理解”NodeList”以及”nameNodeMap”和”HTMLCollection”是理解DOM的关键。
他们是三个集合,而且是动态的,随着DOM的变化得到更新。
以下的例子会进入无限循环
1 | var divs = document.getElementsByTagName("div") ,i, div; |
解决方法是只要把divs.length赋值给len,然后使用i小于len即可。
#第11章 DOM扩展
- Selector API
- HTML5扩展
- 了解专有的DOM扩展
11.1 选择符 API
扩展这个API是为了让原生浏览器支持css查询。
11.1.1 querySelector()方法
接收一个css选择符,返回与该模式匹配的第一个元素。
document.querySelector()在整个文档查找
element.querySelector()则只会在element的后代中查找匹配项。
11.1.2 querySelectorAll()
这个方法与querySelector类似,但是返回的是一个nodeList对象。可以用item()方法来得到各项。1
2
3
4
5
6
7var strong = document.querySelectorAll("p strong");
var i , len;
for(i = 0 , len = strong.length ; i++){
// strong = strong[i];
strong = strong.item(i);
strong.className = "important";
}
11.1.3 matchesSelector()
这个是element类型的一个方法。返回的是一个布尔值。
考虑兼容性问题,封装一个函数里。1
2
3
4
5
6
7function matchesSelector(eleement, selector){
if(element.matchesSelector){
return element.matchesSelector(selector);
} else if(element.msMatchesSelector){
return element.msMatchesSelector(selector);
}......
}
11.2 元素遍历
之前讨论过IE和其他浏览器中关于childNodes中包含空格符的不一致问题,现在为了解决这个问题,Element Traversal规范定义一组新的属性。
属性 | 描述 |
---|---|
childElementCount | 返回子元素,不包含文本节点和注释 |
firstElementChild | 指向第一个子元素 |
lastElementChild | 指向最后一个子元素 |
previousElementSibling | 指向前一个同辈元素 |
nextElememntSibling | 指向下一个同辈元素 |
11.3 HTML5
###11.3.1与类相关的扩充
####1.getElementsByClassName()
H5新加了getElementsByClassName()方法。返回的是得到的所有匹配类的NodeList。1
2var allCurrentUserNames = document.getElementsByClassName("username current");
var selected = document.getElememntById("myDiv").getElementsByClassName("selected");
####2.classList属性
可以使用className属性来添加、删除和替换类名。
一般,如果要把如下中的类b删除1
<div class="a b c"></div>
代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<div class="a b c"></div>
</body>
<script>
var div = document.getElementsByTagName("div")[1];
var className = div.className.split(/\s+/);
console.log(className);
var pos = -1,
i,
len;
for(i = 0,len = className.length; i< len ;i++){
if(className[i] = "b"){
pos = i;
break;
}
}
className.splice(i, 1);
div.className = className.join(" ");
console.log(div.className);
可以有更简单且更安全的方法:HTML5新增的一种操作类名的方式,就是为每个元素添加classList属性。
classList是集合DOMTokenList的实例。类似NodeList。
当然就可以用item()方法来访问了。
同时还定义几个方法:
方法 | 描述 |
---|---|
add(value) | 给定的字符串组添加到列表中。 |
contains(value) | 检测列表中是否存在value类,返回布尔值。 |
remove(value) | 显而易见的意思 |
toggle(value) | 存在就删除,不存在就添加。 |
所以,可见,之前很多行代码可以使用一行就解决了。1
2var div = document.getElementsByTagName("div")[1].classList.remove("b");
console.log(div); //undefined,说明没有返回值。
###11.3.2 焦点管理
H5添加富足管理DOM焦点的功能:document.activeElement属性。
元素获得焦点的方式:
- 页面加载
- 用户输入
- 调用focus()方法
加载期间,activeElement属性是null,加载完之后默认的是document.body。
一个方法:document.hasFocus(),用于确定文档是否获得了焦点。
11.3.3 HTMLDocument的变化
1.readyState属性
有两个可能的值:
- loading
- complete
这个属性最好的用法就是用来判断文档是否加载完全。1
if(document.readyState == "complete"){xxx}
####2.兼容模式
区分渲染页面是标准模式还是混合模式,CompatMode属性来判断:1
2
3
4
5if(document.compatMode == "CSS1Compat"){ //css要大写
alert("标准模式");
} else if(document.compatMode == "BackCompat"){
alert("混合模式");
}
####3.head属性1
var head = document.head || document.getElementsByTagName("head")[0];
11.3.4 字符集属性
charset属性
常用的设置UTF-8的操作就是这个。1
<meta charset="utf-8"/>
可以用document.charset访问到文档中的字符集,默认的是UTF-16。
11.3.5 自定义数据属性
对于H5页面中自定义的属性,原生js可以通过元素的dataset属性来访问自定义的值。这个属性是DONStringMap的实例,是一个名值对的映射。1
2
3
4
5<div id="hzhuang" data-id = "1" data-name = "hzhuang"></div>
</body>
<script>
var div = document.getElementById("hzhuang");
console.log(div);//DOMStringMap {id: "1", name: "hzhuang"}
11.3.6 插入标记
####1. innerHTML属性
兼容性,有的浏览器返回的标签全变成大写。
IE浏览器使用限制:
- 插入script元素必须制定defer属性。
- script元素必须位于有作用域的元素之后。script元素在页面中看不见,所以是无作用域的。
对于解决方法:
1 | div.innerHTML = "_<script defer> |
对于style就类似了。
还有很多元素是不支持这个属性的,如:head,html,style,表格相关元素,title等……
####2.outerHTML属性
与innerHTML相似,但是返回的是包括元素本身的。用于读取或者设置DOM结构。
####3.insertAdjacentHTML()方法
接受插入位置和要插入的HTML两个参数。
接受插入的位置有四个值:beforebegin,afterbegin,beforeend和afterend。
插入位置 | 说明 |
---|---|
beforebegin | 当前元素的前面插入一个紧邻的同辈元素 |
afterend | 当前元素的后面插入一个紧邻的同辈元素 |
afterbegin | 当前元素之下插入一个子元素。第一个子元素之前 |
beforeend | 当前元素之下插入一个子元素。最后的子元素之后 |
1 | div.insertAdjacentHTML("beforebegin", "hzhuang"); |
####4.内存与性能问题
使用这些方法设置新的HTML的时候,之前被删掉的还是占用内存的。
11.3.7 scrolltoView()方法。
通过滚动浏览器窗口或者某个元素,调用元素就可以出现在视口中。
- 传入的参数是true或者没有的话,窗口滚动之后会让调用元素的顶部与视口顶部尽可能齐平。
- 参数是false,则调用元素会尽可能的出现在视口中。
11.4专有扩展
就是不同的浏览器自己做的扩展,但是最终在H5中得到标准化。
###11.4.1 文档模式
###11.4.2 children属性
解决childNodes属性的差异而诞生的,children属性是HTMLCollection的实例。和childNodes很类似,但是IE9中改进,不包括注释节点。
###11.4.3 contains()方法
contains()方法用来检测摸个元素是否有某个后代。1
document.documentElement.contains(document.body);
方法并不是只有这一个,compareDocumentPosition()也可以做到,这个方法是用于确定两个节点之间的关系。返回的是表示关系的数字。
其中参数就是给定的节点。
返回的数字 | 说明 |
---|---|
1 | 表示两个节点无关 |
2 | 给点节点在参考节点的前面 |
4 | 给点节点在参考节点的后面 |
8 | 给定的节点是包含节点的祖先 |
16 | 给点的节点是参考节点的后代 |
1 | var result = document.documentElement.compareDocumentPosition(document.body); |
通用的contains函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function contains(resNode, otherNode){
if(typeof refNode.contains == "function" && (!client.engine.webkit || client.engine.webkit >=552) ){
return resNode.contains(otherNode);
} else if(type compareDocumentPosition == "function"){
return !!(refNode.compareDocumentPosition(other) & 16);//
//!!(x)的作用是如果x是nuu或者undefined的话,返回的是false。
} else{
var node = otherNode.parentNode;
do{
if(node === refNode){
return true;
} else {
node = node.parentNode;
}
}while(node !== null);
}
}
11.4.4 插入文本
innerText和outerText没有被纳入H5的规范。
11.4.5 滚动
几个扩展的方法:
方法 | 说明 |
---|---|
scrollToViewIfNeeded(alignCeter) | 只有在当前的元素在视口中不可见的情况下,才滚动 |
scrollByLines(lineCount) | 将元素的内容滚动到指定的行高 |
scrollByPages(pageCount) | 将元素的内容滚动到指定的页面高度 |
第12章 DOM2和DOM3
引入了更多的交互能力功能以模块的方式引入。
模块:
核心++视图++事件++样式++遍历和范围++HTMl
12.1 DOM变化
检测浏览是否支持DOM模块,可以使用上章中的检测方法。1
2document.implementation.hasFeature("views", "2.0");
...
21.1.1 针对XML命名空间的变化
####1 node类型的变化
属性 | 说明 |
---|---|
localName | 不带命名空间前缀的节点名称 |
nameSpaceURL | 命名空间URL或者null |
prefix | 命名空间前缀 |
DOM3中
#第25章 新兴的API
内容:
- 创建平滑的动画
- 操作文件
- 使用Web Workers在后台执行Js
25.1 requestAnimationFrame( )
早期动画循环
使用setInterval( )来实现。1
2
3
4
5
6
7(function(){
function updateAnimation(){
doAnimation1();
doAnimation2();
};
setInterval(updateAnimation, 100);
})();
问题:
setInterval( )和setTimeout( )都不是十分的精确。传入第二个参数,实际只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列的前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。
###25.1.2 循环间隔的问题
浏览器的计数器的精度:各不相同。
IE9+:4ms/Firefox+safari:10ms/chrome:4ms
所以在不同的浏览器中即使设置相同时间的等待时间,性能也会有区别。
25.1.3 mozRequestAnimationFrame
css变换和动画的优势在于浏览器知道动画什么时候开始,因此会计算出正确的循环讲个。
mozRequestAnimationFrame( )告诉浏览区某些js代码要执行动画。它接受一个参数,即在重绘屏幕前调用的一个函数。1
继续
25.2 Page Visibility API
为了让开发人员知道页面是否对用户可见而推出的。
API组成部分 | 描述 |
---|---|
document.hidden | 页面是否隐藏的布尔值。页面隐藏包括页面在后台标签或者浏览器最小化。 |
document.visibilityState | 4个可能的值:后台或者最小化、前台标签页中、实际的页面已经隐藏,但是 用户可以预览页面和页面在屏幕外执行预渲染处理。 |
visibilitychange事件 | 文档从可见变成不可见或从不可见变成可见时,触发该事件。 |
此API只有IE10和chrome支持,检测的代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function isHiddenSupported(){
return typeof(document.hidden || document.msHidden || document.webkitHidden) != "undefined";
}
function handleVisibilityChange(){
var output = document.getElementById("output");
var msg;
if(document.hidden || document.msHidden || document.webkitHidden){
msg = "hidden";
} else {
msg = "visibility";
}
output.innerHTML += msg;
};
//EventUtil.addHandler是base.js中得到的。
EventUtil.addHandler(document, "msvisibilitychange", handleVisibilityChange);
EventUtil.addhandler(document, "webkitvisibilitychange", handleVisibilityChange);
对于document.visibilityState,IE的值和chrome的值是不一样的,chrome是三个:“hidden”,”visible”,”prerender”。IE暂时忽略,
25.3 Geolocation API
访问之前 必须要得到用户的明确许可,在页面中共享其位置信息。
navigator.geolocation对象,这个对象包含三个方法:
方法 | 描述 | 用法参数 |
---|---|---|
getCurrentPosition( ) | 触发请求用户共享地理定位信息的对话框。 | 三个参数:成功回调函数,可选的失败回调函数以及可选的选项对象。 |
watchPosition( ) | 跟踪用户的信息 | 参数和上一个完全相同 |
clearWatch( ) | 用于取消watchPosition( )的监控 | xx |
成功回调函数会接受到一个Position对象参数;Position对象有两个属性:coords和timestamp。
coords包含的信息:
- latitude:十进制的形式表示的维度
- longitude: 十进制的形式表示的经度
- accuracy:精度
- 其他浏览器还有……
1 | navigator.geolocation.getCurrentPosition(function(position){ |
失败回调函数,返回的是一个对象,有message和code两个属性。message中的信息解释为什么错误;code属性是一个数组,表示的是错误的类型。1
2
3
4
5
6
7
8
9
10navigator.geolocation.getCurrentPosition(function(position){
logdrawMapCenteredAt(position.coords.latitude,position.coords.longitude);
}, function(error){
console.log(error.message);
console.log(error.code);
},{
enableHigeAccuracy: true,
timeout: 5000,
maximumAge: 25000
});
第三个参数用于设定信息的类型。是一个选项对象。
- enableHighAccuracy是一个boolean值,表示必须尽肯呢个的使用最准确的位置信息。
- timeout表示等待位置信息的最长时间。
- maximumAge表示上一次取得坐标信息的有效时间。
watchPosition( )起到定时调用getCurrentPosition( )的效果。
watchPosition( )返回一个数值标识符。如果要取消监控,则使用clearWatch( )方法。
##25.4 File API
在文职输入字段的基础上,添加一些直接访问文件信息的接口。H5在DOM中为文件输入元素添加了一个files集合,files集合中包含一组File对象,每个File对象就对应着一个文件。
File对象的只读属性:
- name
- size
- type
- lastModifiedDate
1 | window.onload = function(){ |
###25.4.1 FileReader类型
读取文件系统提供的方法:
方法 | 描述 |
---|---|
readAsText(file, encoding) | 以纯文本形式读取文件,将读取到的文本保存在result属性中。 |
readAsDataURL(file) | 读取文件并且以数据URL的形式保存在result中。 |
readAsBinaryString(file) | 读取文件并将一个字符串保存在result属性中。 |
readAsArrayBuffer(file) | 读取文件并将一个包含文件内容的ArrayBuffer保存在result中。 |
读取文件也是异步的,所以也会包括几个事件:
- progress:获得lengthComputable,loaded和total属性。
- error:返回error属性。五种错误。
- load:读取成功时候调用。
1 | window.onload = function(){ |
abort( )方法可以停止以上过程。
###25.4.2 读取部分内容
如果想读取文件的部分内容,File对象还支持一个slice( )方法。在火狐浏览器中加入moz,在chrome中要加入webkit。
slice( )支持两个参数:第一个起始字节,第二个是读取的字节数。1
2
3
4
5
6
7
8
9
10
11function blobSlice(blob, startByte, length){
if(blob.slice){
return blob.slice(startByte, length);
}else if(blob.webkit){
return blob.webkitSlice(startByte, length);
} else if(blob.mozslice){
return blob.mozSlice(startByte, length);
} else {
return null;
}
}
Blob类型有一个size属性和type属性。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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54window.onload = function(){
var fileLists = document.getElementById("file-lists");
EventUtil.addHandler(fileLists, "change", function(event){
var info = "",
output = document.getElementById("output"),
progress = document.getElementById("progress"),
files = EventUtil.getTarget(event).files,
type = "default",
reader = new FileReader();
blob = blobSlice(files[0], 0, 32);
if(blob){
reader.readAsText(blob);
reader.onerror = function(){
output.innerHTML = "Could not read file, error code is " + reader.error.code;
};
reader.onprogress = function(event){
if(event.lengthComputable){
progress.innerHTML = event.loaded + "/" + event.total;
}
};
reader.onload = function(){
var html = "";
switch(type){
case "image":
html = "<img src=\"" + reader.result + "\">";
break;
case "text":
heml = reader.result;
break;
}
output.innerHTML = html;
console.log(reader.result);
}
}else{
alert("Your browser doesn't support slice().");
}
});
}
function blobSlice(blob, startByte, length){
if(blob.slice){
return blob.slice(startByte, length);
}else if(blob.webkit){
return blob.webkitSlice(startByte, length);
} else if(blob.mozslice){
return blob.mozSlice(startByte, length);
} else {
return null;
}
}
###25.4.3 对象URL