JS 红宝书 · 读书笔记 -- 上篇

3,978 阅读29分钟

JavaScript 高级程序设计

个人博客:yeaseonzhang.github.io

花了半个多月的时间,终于又把“JS红宝书”又撸了一遍。

第一次读“JS红宝书”还是2015年初学JS的时候,那时候只是把语法部分读了一遍,还有一些浏览器相关知识做了下了解,大概也就读了半本的样子,
就开始了用JS进行开发了,在成长的道路上遇见了JQuery,当时真的是感觉到JQuery太友好了,慢慢放下了原生开发。

现在呢,更多的时候是在用框架进行开发,越来越觉得自己的JS基础很缺乏,然后就开启了“JS红宝书”二刷之路。

下面就把书中自己觉得重要的、没有掌握的知识整理出来。因为我觉得还是会三刷“JS红宝书”,希望把这本700多页的书越读越薄,勉励。

章节

  • 在HTML中使用JavaScript
  • 基本概念
  • 变量、作用域和内存问题
  • 引用类型
  • 面向对象的程序设计
  • 函数表达式
  • BOM
  • DOM
  • DOM 扩展
  • DOM2 和 DOM3

在HTML中使用JavaScript


async

加载外部脚本文件,通知浏览器立即下载,异步执行。

noscript 元素

noscript标签显示条件:

  • 浏览器不支持脚本
  • 浏览器支持脚本,但是脚本被禁用

基本概念


语法

标识符

  • 第一个字符必须是一个字母、下划线或者一个美元符号
  • 其他字符可以是字母、下划线、美元或者数字

严格模式

支持严格模式的浏览器包括:IE10+、Firefox4+、Safari 5.1+、Opera 12+和Chrome。

数据类型

undefined

对未初始化的变量执行typeof操作会返回undefined值,而对于未声明的变量执行typeof操作同样会返回undefined值。

null

typeof null    // -> object

undefined值派生自null值。

console.log(null == undefind)    // -> true

isFinite()

测试一个数值是不是无穷值。

Number.NEGATIVE_INFINITY:负无穷
Number.POSITION_INFINITY:正无穷

NaN

在ECMAScript中,任何数值除以0会返回NaN

isNaN()接受一个参数,确定这个参数是否"不是数值"。

数值转换

Number()

  • 如果是null,返回0
  • 如果是undefined,返回NaN

parseInt()
在ES5 中不支持解析八进制的能力。

parseInt('070');    // -> 70 not 56

通过第二个参数,指定转换基数(进制)默认十进制。

字符串

  • ECMAScript中的字符串是不可变的
  • toString()

在调用数值的toString方法,可以传递一个参数:输出数值的基数。没有toString方法的则返回该值的字面量

var num = 10;
console.log(num.toString(2));    // -> '1010'

object类型

Object类型的属性方法:

  • constructor
  • hasOwnProperty(propertyName)
  • isPrototypeOf(obj)
  • propertyIsEnumerable(propertyName)
  • toLocalString()
  • toString()
  • valueOf()

操作符

  • ++ or --

前置与后置的区别

var num1 = 2;
var num2 = 20;
var num3 = --num1 + num2;    // 21
var num4 = num1 + num2;        // 21

var num5 = 2;
var num6 = 20;
var num7 = num1-- + num2;    // 22
var num8 = num1 + num2;        // 21
  • 一元加操作符用于强制类型转换,隐式Number()效果

for-in 语句

for-in语句是一种精确的迭代语句,可以用来枚举对象的属性。

通过for-in循环输出的属性名的顺序是不可预测的。

如果要迭代的对象的变量值为nullundefinedfor-in语句会抛出错误。ES5更正了这一行为,不再抛出错误,只是不再执行循环体。

建议:在是使用for-in循环之前,先检查对象值是不是null或者undefined

变量、作用域和内存问题


基本类型和引用类型

复制变量值

  • 复制基本类型值,这两个变量相互独立,互不影响。
  • 复制引用类型(对象),值引用是一个指针,改变其中一个对象,会影响另一个对象。

传递参数

function setName(obj) {
    obj.name = "Nicholas";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"

以上代码中创建一个对象,并将其保存在了变量person 中。然后,这个变量被传递到setName()函数中之后就被复制给了obj。在这个函数部,objperson 引用的是同一个对象。换句话说,即使这个变量是按值传递的,obj 也会按引用来访问同一个对象。于是,当在函数内部为obj 添加name属性后,函数外部的person 也将有所反映;因为person 指向的对象在堆内存中只有一个,而且是全局对象。有很多开发人员错误地认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。为了证明对象是按值传递的,我们再看一看下面这个经过修改的例子:

function setName(obj) {
    obj.name = "Nicholas";
    obj = new Object();
    obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"

这个例子与前一个例子的唯一区别,就是在setName()函数中添加了两行代码:一行代码为obj重新定义了一个对象,另一行代码为该对象定义了一个带有不同值的name属性。在把person传递给setName()后,其name 属性被设置为"Nicholas"。然后,又将一个新对象赋给变量obj,同时将其name属性设置为"Greg"。如果person 是按引用传递的,那么person 就会自动被修改为指向其name属性值为"Greg"的新对象。但是,当接下来再访问person.name 时,显示的值仍然是"Nicholas"。这说明即使在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。

检测类型

虽然在检测基本数据类型时typeof 是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大。通常,我们并不是想知道某个值是对象,而是想知道它是什么类型的对象。为此,ECMAScript提供了instanceof 操作符。

延长作用域

  • try-catch语句中的catch
  • with语句

小结

  • 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
  • 从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;
  • 引用类型的值是对象,保存在堆内存中;
  • 包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针;

引用类型


Array类型

检测数组

if (value instanceof Array) {

}

ECMAScript5新增了 Array.isArray()方法

if (Array.isArray(value)) {

}

sort方法

该方法有缺陷,sort()方法会调用每个数组项的toString()转型方法,然后比较字符串进行排序。

var values = [0, 1, 5, 10, 15];
values.sort();
alert(values); //0,1,10,15,5

因此sort()方法接受一个比较函数作为参数。

function compare(value1, value2) {
    if (value1 < value2) {
        return -1;
    } else if (value1 > value2) {
        return 1;
    } else {
        return 0;
    }
}

var values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values); //0,1,5,10,15

splice方法

splice方法始终返回一个数组,该数组包含了从原始数组中删除的项。

var colors = ["red", "green", "blue"];
var removed = colors.splice(0,1); // 删除第一项
alert(colors); // green,blue
alert(removed); // red,返回的数组中只包含一项

removed = colors.splice(1, 0, "yellow", "orange"); // 从位置1 开始插入两项
alert(colors); // green,yellow,orange,blue
alert(removed); // 返回的是一个空数组

removed = colors.splice(1, 1, "red", "purple"); // 插入两项,删除一项
alert(colors); // green,red,purple,orange,blue
alert(removed); // yellow,返回的数组中只包含一项

迭代方法

ECMAScript5为数组定义了5个迭代方法。

  • every(): 对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true。
  • filter(): 对数组中的每一项运行给定函数,返回该函数会返回true 的项组成的数组。
  • forEach(): 对数组中的每一项运行给定函数。这个方法没有返回值。
  • map(): 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
  • some(): 对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。

归并方法

ECMAScript 5 还新增了两个归并数组的方法。

  • reduce()
  • reduceRight()

reduce()reduceRight()的函数接收4 个参数:前一个值、当前值、项的索引和数组对象。

var values = [1, 2, 3, 4, 5];
var sum = values.reduce((prev, cur, index, array) => {
    return prev + cur;
});
console.log(sum);

RegExp类型

正则表达式中的元字符

( [ { \ ^ $ | ) ? * + . ] }

:匹配元字符必须转义

RegExp 构造函数

接受两个参数: 一个是要匹配的字符串模式,另一个是可选的标志字符串。

var pattern1 = /[bc]at/i;
// 等价于
var pattern2 = new RegExp('[bc]at', 'i');

:由于RegExp构造函数的模式是字符串,所以在某些情况下要对字符串进行双重转义,所有元字符都必须双重转义。例如\n在字符串中被转义为\n,而在正则表达式字符串中就会变成\\n

RegExp实例方法

exex()方法

该方法是专门为捕获组而设计的。exec()接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回null。返回的数组虽然是Array 的实例,但包含两个额外的属性:indexinput。其中,index 表示匹配项在字符串中的位置,而input 表示应用正则表达式的字符串。在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,则该数组只包含一项)。

var text = "mom and dad and baby";
var pattern = /mom( and dad( and baby)?)?/gi;

var matches = pattern.exec(text);
alert(matches.index); // 0
alert(matches.input); // "mom and dad and baby"
alert(matches[0]); // "mom and dad and baby"
alert(matches[1]); // " and dad and baby"
aler t(matches[2]); // " and baby"

对于exec()方法而言,即使在模式中设置了全局标志(g),它每次也只会返回一个匹配项。在不设置全局标志的情况下,在同一个字符串上多次调用exec()将始终返回第一个匹配项的信息。而在设置全局标志的情况下,每次调用exec()则都会在字符串中继续查找新匹配项。

test()方法

接受一个字符串参数。在模式与该参数匹配的情况下返回true;否则,返回false

var text = "000-00-0000";
var pattern = /\d{3}-\d{2}-\d{4}/;

if (pattern.test(text)){
    alert("The pattern was matched.");
}

RegExp实例继承的toLocaleString()toString()方法都会返回正则表达式的字面量,与创建正则表达式的方式无关。

var pattern = new RegExp("\\[bc\\]at", "gi");
alert(pattern.toString()); // /\[bc\]at/gi
alert(pattern.toLocaleString()); // /\[bc\]at/gi

Function类型

函数声明与函数表达式

解析器会率先读取函数声明,并使其在执行任何代码之前可用;至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

// ok
alert(sum(10,10));
function sum(num1, num2){
    return num1 + num2;
}

// unexpected identifier(意外标识符)
alert(sum(10,10));
var sum = function(num1, num2){
    return num1 + num2;
};

:要访问函数的指针而不执行函数的话,必须去掉函数名后的那对圆括号。

函数内部属性

  • arguments
  • this

arguments具有一个callee属性,该属性是一个指针,指向拥有这个arguments对象的函数。

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

等价于

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

达到一种解耦的效果。

ECMAScript 5也规范了一个函数对象属性:caller(看着很像callee),这个属性中保存着调用当前函数的函数的引用,如果实在全局作用域中调用当前函数,它的值为null

function outer(){
    inner();
}
function inner(){
    alert(inner.caller);
}
outer();

inner.caller指向outer()。为了实现更松散的耦合,也可以通过argument.callee.caller来访问相同的信息。

function outer() {
    inner();
}
function inner() {
    alert(arguments.callee.caller);
}
outer();

:当函数在严格模式下运行时,访问arguments.callee 会导致错误。ECMAScript 5 还定义了arguments.caller属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是undefined。定义这个属性是为了分清arguments.caller 和函数的caller 属性。以上变化都是为了加强这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其他代码了。
严格模式还有一个限制:不能为函数的caller 属性赋值,否则会导致错误。

函数属性和方法

每个函数都包含两个属性:

  • length: 表示函数希望接收的命名参数的个数
  • prototype: 保存实例方法

每个函数都包含两个非继承而来的方法:

  • apply()
  • call()
    这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this 对象的值。首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array 的实例,也可以是arguments对象。
    call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()方法而言,第一个参数是this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来。

:在严格模式下,未指定环境对象而调用函数,则this 值不会转型为window。除非明确把函数添加到某个对象或者调用apply()call(),否则this 值将是undefined

在非严格模式下,callapply的第一个参数传递为nullundefined时,函数体内的this会指向默认的宿主对象,在浏览器中则是window

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

基本包装类型

使用new调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的。

var value = '25';
var number = Number(value);    // 转型函数
console.log(typeof number);    // 'number'

var obj = new Number(value); // 构造函数
console.log(typeof obj);    // 'object'

Number类型

Number类型的toString()方法很特别,可以传递一个表示基数的参数。

var num = 10;
alert(num.toString()); //"10"
alert(num.toString(2)); //"1010"
alert(num.toString(8)); //"12"
alert(num.toString(10)); //"10"
alert(num.toString(16)); //"a"

String类型

字符方法
  • charAt()
  • charCodeAt()
var stringValue = "hello world";

alert(stringValue.charAt(1)); //"e"
alert(stringValue.charCodeAt(1)); //输出字符编码"101"
字符串操作方法
  • concat()
  • slice()
  • substr()
  • substring()

这些方法对原字符均没有任何影响。

var stringValue = "hello ";
var result = stringValue.concat("world", "!");
alert(result); //"hello world!"

var stringValue = "hello world";
alert(stringValue.slice(3)); //"lo world"
alert(stringValue.substring(3)); //"lo world"
alert(stringValue.substr(3)); //"lo world"
alert(stringValue.slice(3, 7)); //"lo w"
alert(stringValue.substring(3,7)); //"lo w"
alert(stringValue.substr(3, 7)); //"lo worl"

// 参数是负值的情况下,它们的行为就不尽相同了。
// 其中,slice()方法会将传入的负值与字符串的长度相加,
// substr()方法将负的第一个参数加上字符串的长度,而将负的第二个参数转换为0。
// 最后,substring()方法会把所有负值参数都转换为0。
alert(stringValue.slice(-3)); //"rld"
alert(stringValue.substring(-3)); //"hello world"
alert(stringValue.substr(-3)); //"rld"
alert(stringValue.slice(3, -4)); //"lo w"
alert(stringValue.substring(3, -4)); //"hel"
alert(stringValue.substr(3, -4)); //""(空字符串)
字符串位置方法
  • indexOf()
  • lastIndexOf()

两个方法的第二个参数,表示从字符串中哪个位置开始搜索。

trim()方法

ECMAScript 5方法

字符串转换大小写方法
  • toLowerCase()
  • toLocaleLowerCase()
  • toUpperCase()
  • toLocaleUpperCase()
字符串的模式匹配方法
  • match()
  • search()
  • replace()
  • split()

match()方法,在字符串上调用这个方法,本质上和调用RegExpexec()方法相同。match()方法只接受一个参数,要么是一个正则表达式,要么是一个RegExp对象。

var text = 'cat, bat, sat, fat';
var pattern = /.at/;

// 等价于 pattern.exec(text)
var matches = text.match(pattern);
alert(matches.index); //0
alert(matches[0]); //"cat"
alert(pattern.lastIndex); //0

search()方法的参数与match()方法相同,该方法返回字符串中第一个匹配项的索引,没有匹配项返回-1;个人认为serch()就是正则版的indexOf()

var text = "cat, bat, sat, fat";
var pos = text.search(/at/);
aler t(pos); //1

ECMAScript提供了replace()方法,该方法接受两个参数,第一个参数可以是RegExp对象或者是一个字符串,第二个参数可以是一个字符串或者一个函数。

var text = "cat, bat, sat, fat";
var result = text.replace("at", "ond");
alert(result); //"cond, bat, sat, fat"

result = text.replace(/at/g, "ond");
aler t(result); //"cond, bond, sond, fond"
字符序列 替换文本
? $
$& 匹配整个模式的子字符串。RegExp.lastMatch
$' 匹配子字符串之前的字符串。RegExp.leftContext
$` 匹配的子字符串之后的字符串。 RegExp.rightContext
$n 匹配第n个捕获组的子字符串 n: 0~9
$nn 匹配第nn个捕获组的子字符串 nn: 01~99
var text = "cat, bat, sat, fat";
result = text.replace(/(.at)/g, "word ($1)");
alert(result); //word (cat), word (bat), word (sat), word (fat)

split()方法可以基于指定的分隔符(字符串 or RegExp对象)将一个字符串分割成多个子字符串,并将结构放在一个数组中。可以接受可选的第二个参数,用于指定数组的大小。

var colorText = "red,blue,green,yellow";
var colors1 = colorText.split(","); //["red", "blue", "green", "yellow"]
var colors2 = colorText.split(",", 2); //["red", "blue"]
var colors3 = colorText.split(/[^\,]+/); //["", ",", ",", ",", ""]
localeCompare()方法

比较两个字符串,并返回下列值中的 一个:

  • 如果字符串在字母表中应该排在字符串参数之前,则返回一个负数
  • 如果字符串等于字符串参数,则返回0;
  • 如果字符串在字母表中应该排在字符串参数之后,则返回一个正数
var stringValue = "yellow";
alert(stringValue.localeCompare("brick")); //1
alert(stringValue.localeCompare("yellow")); //0
alert(stringValue.localeCompare("zoo")); //-1
fromCharCode()方法

这个方法的任务是接收一个或多个字符编码,然后将它们转换成一个字符串。相当于charCodeAt()反操作。

alert(String.fromCharCode(104, 101, 108, 108, 111)); //"hello"

Math 对象

  • min()
  • max()
  • ceil()
  • floor()
  • round()
  • random()

面向对象的程序设计

理解对象

属性类型

数据类型
  • [[Configurable]]: 表示能否通过delete删除属性从而重新定义属性,能够修改属性的特性,或者能否把属性修改为访问器属性
  • [[Enumerable]]: 表示能否通过for-in循环返回属性
  • [[Writable]]: 表示能否修改属性的值
  • [[Value]]: 包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。默认值undefined

要修改属性默认的特性,必须使用ECMAScript 5Object.defineProperty()方法。这个方法接受三个参数:属性所在对象,属性名和一个描述符对象。其中描述符对象的属性值必须是:configurableenumerablewritablevalue。设置其中一个或多个。

var person = {};
Object.defineProperty(person, 'name', {
    writable: false,
    value: 'Yeaseon'
});

Object.defineProperty()方法不能对configurable: false的对象进行修改。

访问器属性
  • [[Configurable]]: 表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。
  • [[Enumerable]]: 表示能否通过for-in循环返回属性。
  • [[Get]]: 在读取属性时调用的函数,默认undefined
  • [[Set]]: 在写入属性时调用的函数,默认undefined
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.edition); //2

读取属性的特性

ECMAScript 5Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。该方法接收两个参数:属性所在的对象和要读取器描述符的属性名称,返回值是对象。

创建对象

工厂模式

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 Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

构造函数模式

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 Engineer");
var person2 = new Person("Greg", 27, "Doctor");

原型模式

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(); //"Nicholas"

Person.prototype.constructor会指向Personperson1并没有直接连接到构造函数Person


  • 可以通过isPrototypeOf()方法来确定对象之间是否存在原型关系。从本质上讲,[[Prototype]]指向调用isPrototypeOf()方法的对象Person.prototype,则会返回true
alert(Person.prototype.isPrototypeOf(person1)); //true

ECMAScript 5增加了Object.getPrototypeOf()方法,该方法返回[[Prototype]]的值。

alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"

: 虽然可以通过对象实例person1访问保存在原型中的值,但却不能重写原型中的值。

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"; //实质是在实例上增加一个name属性
alert(person1.name); //"Greg"——来自实例
alert(person2.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();

person1.name = "Greg";
alert(person1.name); //"Greg"——来自实例

delete person1.name;
alert(person1.name); //"Nicholas"——来自原型
  • hasOwnProperty()方法可以检测一个属性是不是存在于实例,是则返回true

  • in操作符
    (prop in obj)通过in操作符可以判定对象是否有该属性,不论是本身含有还是原型含有,都返回true
    可以通过in配合hasOwnProperty()确定该属性是存在于对象中还是原型中:

    function detectProperty(obj, name) {
      if (name in obj) {
          obj.hasOwnProperty(name) ? '在对象中' : '在原型中';
      } else {
          console.log('不含有该属性');
      }
    }
  • ECMAScript 5Object.keys()方法可以取得对象上所有可枚举的实例属性。

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

var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); //"name,age"
  • Object.getOwnPropertyNames会得到所有实例属性,不论是否可枚举。
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"

简化Person.prototype写法:

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

这样写有一个缺陷,constructor属性则会等于Object,我们需要手动设置constructor

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

但这同时也会导致constructor[[Enumerable]]特性变成了true,默认情况下是false。再修改下写法:

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

Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});

原型重写会导致构造函数与最初原型之间的联系切断。

function Person(){
}
var friend = new Person();
Person.prototype = {  //重写
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
friend.sayName(); //error

结合使用构造函数和原型模式

用构造函数模式定义实例属性,用原型模式定义方法和共享属性。

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.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

继承

构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型的内部指针。

原型链

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

SuperType.prototype.getSuperValue = function () {
    return this.property;
};

function SubType () {
    this.subproperty = false;
}

SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function () {
    return this.subproperty;
};

var instance = new SubType();
console.log(instance.getSuperValue());  // true

instanceof操作符

用来确定原型和实例之间的关系。

alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true

第二种方式就是isPrototypeOf()方法,只要原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。

alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true

函数表达式

由于有声明提升的存在,定义函数不要放在条件表达式中

if (condition) {
    function sayHi () {
        console.log('Hi');
    }
} else {
    function sayHi () {
        console.log('Yo');
    }
}

ECMAScript中属于无效语法,在不同浏览器中修正的做法并不一致。推荐的写法,如下:

var sayHi;

if (condition) {
    sayHi = function () {
        console.log('Hi');
    }
} else {
    sayHi = function () {
        console.log('Yo');
    }
}

这种函数表达式不存在声明提升,所以OK。

递归

函数作用域链

当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象。在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位...,直到作用域终点的全局执行环境。

闭包

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

:在闭包中使用this对象可能会导致一些问题。匿名函数的执行环境具有全局性,因此其this对象通常指向window

var name = 'The window';

var obj = {
    name: 'my object',
    getNameFunc: function () {
        return function () {
            return this.nam;
        }
    }
}

console.log(obj.getNameFunc()());  // The Window (非严格模式)

模仿块级作用域

块级作用域

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

BOM


window 对象

全局作用域

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

窗口位置

获得窗口左边和上边的位置。

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

Firefox支持screenXscreenY,其他浏览器均支持screenLeftscreenTop

但是还是需要注意一个问题:在IE Opera中,screenLeft screenTop 保存的的是可见区域的距离,也就是我们浏览器中不包含工具栏的区域与屏幕的距离;在ChromeFirefoxSafariscreenYscreenTop返回的是整个浏览器窗口相对于屏幕坐标的值。

窗口大小

IE9+、Firefox、Safari、Opera和Chrome均提供了4个属性innerWidthinnerHeightouterWidthouterHeight

  • IE9+、Safari和Firefox中,outerWidthouterHeight返回浏览器窗口本身的尺寸,而innerWidthinnerHeight则表示该容器中页面视图区的大小(减去边框宽度)
  • Chrome中,inner*outer*返回相同的值,即视口大小而非浏览器窗口的大小。
  • 在IE、Firefox、Safari、Opera和Chrome中,都能通过document.documentElement.clientWidthdocument.documentElement.clientHeight中保存了页面视口信息。

获取页面视口大小

var pageWidth = window.innerWidth,
    pageHeight = window.innerHeight;

if (typeof pageWidth != 'number') {
    if (document.compatMode == 'CSS1Compat') { // 浏览器标准模式
        pageWidth = document.documentElement.clientWidth;
        pageHeight = document.documentElement.clientHeight;
    } else {  // IE6 混杂模式
        pageWidth = document.body.clientWidth;
        pageHeight = document.doby.clientHeight;
    }
}
  • resizeTo()接受浏览器窗口的新宽度和新高度
  • resizeBy()接受新窗口与原窗口的宽度和高度差。

这两个方法可能被浏览器禁用。

导航

如果是浏览器内置的屏蔽程序组织的弹出窗口,那么window.open()很可能会返回null

var newWindow = window.open('https://www.google.com.hk', '_blank');
if (newWindow == null) {
    console.log('The popup was blocked!');
}

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

var blocked = false;

try {
    var newWindow = window.open('https://www.google.com.hk', '_blank');
    if (newWindow == null) {
        blocked = true;
    }
} catch (ex) {
    blocked = true;
}

if (blocked) {
    console.log('The popup was blocked');
}

location 对象

location对象的属性

  • hash
  • host
  • hostname:与host不同的是,不带端口号
  • href
  • pathname: 返回URL中的目录和(或)文件名
  • port
  • protocol
  • search:返回URL的查询字符串,这个字符串?开头

navigator 对象

location对象的属性

  • appCodeName: 浏览器的名称,通常都是Mozilla
  • appMinorVersion:此版本信息
  • appName: 完整的浏览器名称
  • appVersion:浏览器的版本
  • buildID:浏览器编译版本
  • cookieEnabled:表示cookie是否可用
  • cpuClass:客户端计算机中使用的CPU类型
  • javaEnabled():表示当前浏览器中是否启用了java
  • language: 浏览器的主语言
  • mimeTypes:浏览器中注册的MIME类型数组
  • onLine:表示浏览器是都连接到因特网
  • oscpu:客户端计算机的操作系统或使用的CPU
  • platform:浏览器所在的系统平台
  • plugins:浏览器中安装的插件信息的数组
  • preference():设置用户的首选项
  • systemLanguage:操作系统的语言
  • userAgent:浏览器的用户代理字符串

DOM


节点层次

Node类型

每个节点都有一个nodeType属性,用于表明节点的类型。

  • Node.ELEMENT_NODE(1)
  • Node.ATTRIBUTE_NODE(2)
  • Node.TEXT_NODE(3)
  • Node.CDATA_SECTION_NODE(4)
  • Node.ENTITY_REFERENCE_NODE(5)
  • Node.ENTITY_NODE(6)
  • Node.PROCESSING_INSTRUCTION_NODE(7)
  • Node.COMMENT_NODE(8)
  • Node.DOCUMENT_NODE(9)
  • Node.DOCUMENT_TYPE_NODE(10)
  • Node.DOCUMENT_FRAGMENT_NODE(11)
  • Node.NOTATION_NODE(12)

为了确保跨浏览器兼容,将nodeType属性与数字值进行比较:

if (someNode.nodeType == 1) {
    console.log('Node is an element');
}
  • nodeName属性
    if (someNode.nodeType == 1) {
      var value = someNode.nodeName;  // nodeName的值是元素的标签名
    }
节点关系
  • childNodes属性

每个节点都有一个childNodes属性,其中保存着一个NodeList对象,该对象是一种类数组对象。

  • parentNode属性

每个节点都有一个parentNode属性,该属性指向文档树中的父节点。包含在childNodes列表中的每个节点相互都是兄弟节点。使用previousSiblingnextSibling属性,可以访问其他兄弟节点。

:列表中第一个节点的previousSibling属性值为null,同理列表中最后一个节点的nextSibling属性也是null。父节点的firstChildlastChild属性分别指向其childNodes列表中的第一个和最后一个节点。如果不存在则为null

hasChildNodes()方法在节点包含一个或多个子节点的情况下返回true,比查询childNodes.length更简便。

最后一个属性ownerDocument,该属性指向表示整个文档的文档节点(root),直接返回根节点不需要一层层向上回溯。

操作节点
  • appendChild()

用于向childNodes列表的末尾添加一个节点。

var returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); //true
alert(someNode.lastChild == newNode); //true

任何DOM节点不可能同时出现在多个位置。

//someNode 有多个子节点
var returnedNode = someNode.appendChild(someNode.firstChild);
alert(returnedNode == someNode.firstChild); //false
alert(returnedNode == someNode.lastChild); //true
  • insertBefore()

把节点放在指定位置,该方法接受两个参数:要插入的节点和作为参考的节点。插入节点后,被插入的节点会变成参照节点的前一个兄弟节点。参照节点是null的话,insertBeforeappendChild执行相同的操作,都插入列表末尾。

//插入后成为最后一个子节点
returnedNode = someNode.insertBefore(newNode, null);
alert(newNode == someNode.lastChild); //true

//插入后成为第一个子节点
var returnedNode = someNode.insertBefore(newNode, someNode.firstChild);
alert(returnedNode == newNode); //true
alert(newNode == someNode.firstChild); //true

//插入到最后一个子节点前面
returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
alert(newNode == someNode.childNodes[someNode.childNodes.length-2]); //true
  • replaceChild()

替换节点,接受两个参数:要插入的节点和要替换的节点。

//替换第一个子节点
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);

//替换最后一个子节点
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
  • removeChild()

移除节点,接受一个参数:要被移除的节点。

//移除第一个子节点
var formerFirstChild = someNode.removeChild(someNode.firstChild);

//移除最后一个子节点
var formerLastChild = someNode.removeChild(someNode.lastChild);
  • cloneNode()

复制节点,接受一个布尔值,表示是否深复制。复制后返回的节点没有父节点,可以通过插入等操作手动指定。

var deepList = myList.cloneNode(true);
alert(deepList.childNodes.length); //3(IE < 9)或7(其他浏览器)

var shallowList = myList.cloneNode(false);
alert(shallowList.childNodes.length); //0

cloneNode方法不会复制DOM节点的js属性。IE存在一个bug,它会复制事件处理程序。

  • normalize()

稍后讨论

以上方法的返回值,都是被操作的节点。

Document类型

Document节点具有下列特征:

  • nodeType的值为9
  • nodeName的值为#document
  • nodeValue的值为null
  • parentNode的值为null
  • ownerDocument的值为null
  • 其子节点可能是一个DocumentType(最多一个)、Element(最多一个)、ProcessingInstructionComment
// 通过`documentElement`属性访问<html>元素
var html = document.documentElement;
// 访问 <body> 元素
var body = document.body;
// <!DOCTYPE>
var doctype = document.doctype;
// <title>
var title = document.title;
// 完整 url
var url = document.URL;
// domain 域名
var domain = document.domain;
// 取得来源页面的URL(也就是导航到这页的页面)
var referrer = document.referrer;

查找元素的方法:

  • document.getElementById()
  • document.getElementsByTagName()
  • document.getElementsByName()

文档写入:

  • document.write()
  • document.writeln()在字符串尾加换行符(\n)
<script type="text/javascript">
    document.write("<script type=\"text/javascript\" src=\"file.js\">" + "<\/script>");
</script>

Element类型

Element类型提供了对元素标签名、子节点及特性的访问。

  • nodeType的值为1
  • nodeName的值为元素的标签名
  • nodeValue的值为null
  • parentNode可能是DocumentElement
  • 其子节点可能是ElementTextCommentProcessingInstructionCDATASectionEntityReference

访问元素的标签名,可以使用nodeName属性,也可以使用tagName属性,后者更直观。

<div id="myDiv"></div>

var div = document.getElementById("myDiv");
alert(div.tagName); //"DIV"
alert(div.tagName == div.nodeName); //true

操作特性的方法:

  • getAttribute()
  • setAttribute()
  • removeAttribute()

attributes属性

Element类型是使用attributes属性的唯一一个DOM节点属性。attributes属性包含一个NamedNodeMap。元素的每一个特性都由一个Attr节点表示,每个节点都保存在NamedNodeMap对象中。

NamedNodeMap对象的方法:

  • getNamedItem(name):返回nodeName属性等于name的节点
  • removeNamedItem(name):从列表中移除nodeName属性等于name的节点
  • setNamedItem(node):向列表中添加节点,以节点的nodeName属性为索引
  • item(pos):返回位于数字pos位置处的节点

attributes属性中包含一系列节点,每个节点的nodeName就是特性的名称,而节点nodeValue就是特性的值。

var id = element.attributes.getNamedItem('id').nodeValue;
// 简写
var id = element.attributes['id'].nodeValue;

创建元素
document.createElement()方法可以创建新元素,这个方法接受一个参数(标签名)

var div = document.createElement('div');

Text类型

文本节点由Text类型表示,包含的是可以照字面解释的纯文本内容。纯文本中可以包含转义后的HTML字符,但不能包含HTML代码。

  • nodeType的值为3
  • nodeName的值为#text
  • nodeValue的值为节点所包含的文本
  • parentNode是一个Element

操作节点中的文本:

  • appendData(text):将text添加到节点的末尾
  • deleteData(offset, count):从offset指定的位置开始删除count个字符
  • insertData(offset, text):在offset指定的位置插入text
  • replaceData(offset, count, text):用text替换从offset指定的位置开始到offset+count为止的文本
  • splitText(offset):从offset指定的位置将当前文本分成两个文本节点
  • substringData(offset, count):提取从offset指定的位置开始到offset+count为止处的字符串。

在向DOM文档中插入文本之前,应该先对其进行HTML编码

创建文本节点

  • document.createTextNode()
var textNode = document.createTextNode("<strong>Hello</strong> world!");

DOM 操作技术

使用函数实现加载外部JS文件

function loadScript(url) {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;
    document.body.appendChild(script);
}
loadScirpt('xx.js');

IE将<script>视为一个特殊的元素,不允许DOM访问其子节点。不过可以使用<script>元素的text属性指定JS代码。

操作表格

// create table
var table = document.createElement('table');
table.border = 1;
table.width = '100%';

// create tbody
var tbody = document.createElement('tbody');
table.appendChild(tbody);

// create row1
var row1 = document.createElement('tr');
tbody.appendChild(row1);

var cell1_1 = document.createElement('td');
cell1_1.appendChild(document.createTextNode('Cell 1,1'));
row1.appendChild(cell1_1);

var cell2_1 = document.createElement('td');
cell2_1.appendChild(document.createTextNode('Cell 2,1'));
row1.appendChild(cell2_1);

// create row2
var row2 = document.createElement('tr');
tbody.appendChild(row2);

var cell1_2 = document.createElement('td');
cell1_2.appendChild(document.createTextNode('Cell 1,2'));
row1.appendChild(cell1_2);

var cell2_2 = document.createElement('td');
cell2_2.appendChild(document.createTextNode('Cell 2,2'));
row1.appendChild(cell2_2);

document.body.appendChild(table);

DOM 扩展


选择符 API

  • querySelector()方法

querySelector()方法接受一个CSS选择符,返回与该模式匹配的第一个元素,若没有,返回null

可以通过Document类型调用,也可以通过Element类型调用,后者只会在该元素后代元素的范围内查找匹配的元素。

  • querySelectorAll()方法

querySelectorAll()方法返回的是所有匹配的元素,是一个NodeList实例。

  • matchesSelector()方法

Element类型新增的一个方法,接受一个参数CSS选择符,如果调用元素与该选择符匹配,返回true,否则返回false

元素遍历

  • childElementCount:返回子元素(不包含文本节点和注释)的个数
  • firstElementChild:指向第一个子元素
  • lastElementChild:指向最后一个子元素
  • previousElementSibling:指向前一个兄弟元素
  • nextElementSibling:指向后一个兄弟元素

不同于前面的返回节点的方法。

// 节点版本
var i,
    len,
    child = element.firstChild;
while(child != element.lastChild){
    if (child.nodeType == 1){ //检查是不是元素
        processChild(child);
    }
    child = child.nextSibling;
}
// 元素版本
var i,
    len,
    child = element.firstElementChild;
while(child != element.lastElementChild){
    processChild(child); //已知其是元素
    child = child.nextElementSibling;
}

HTML5

  • getElementsByClassName()方法
  • classList属性,这个属性是新集合类型DOMTokenList的实例。
    • add(value)
    • contains(value)
    • remove(value)
    • toggle(value)
div.classList.remove("user");

焦点管理

document.activeElement属性,始终会引用DOM中前端获得了焦点的元素。

var button = document.getElementById("myButton");
button.focus();
alert(document.activeElement === button); //true

document.hasFocus()方法,可以确定文档是否获得了焦点。

var button = document.getElementById("myButton");
button.focus();
alert(document.hasFocus()); //true

HTMLDocument的变化

+ `readyState`属性有两个值,`loading`和`complete`
if (document.readyState == 'complete') {
    // 加载完成
}

document.charset字符集属性

data-自定义数据属性

<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>

var div = document.getElementById("myDiv");

//取得自定义属性的值
var appId = div.dataset.appId;
var myName = div.dataset.myname;

//设置值
div.dataset.appId = 23456;
div.dataset.myname = "Michael";

innerHTML属性

在读模式下,innerHTML 属性返回与调用元素的所有子节点(包括元素、注释和文本节点)对应
的HTML 标记。在写模式下,innerHTML 会根据指定的值创建新的DOM树,然后用这个DOM树完全
替换调用元素原先的所有子节点

outerHTML属性

在读模式下,outerHTML 返回调用它的元素及所有子节点的HTML 标签。在写模式下,outerHTML
会根据指定的HTML 字符串创建新的DOM 子树,然后用这个DOM子树完全替换调用元素。

insertAdjacentHTML()方法

插入元素的新增方法,接受两个参数,插入的位置和要插入的HTML文本,第一个参数的值:

  • 'beforebegin'
  • 'afterbegin'
  • 'beforeend'
  • 'afterend'
//作为前一个同辈元素插入
element.insertAdjacentHTML("beforebegin", "<p>Hello world!</p>");

//作为第一个子元素插入
element.insertAdjacentHTML("afterbegin", "<p>Hello world!</p>");

//作为最后一个子元素插入
element.insertAdjacentHTML("beforeend", "<p>Hello world!</p>");

//作为后一个同辈元素插入
element.insertAdjacentHTML("afterend", "<p>Hello world!</p>");

scrollIntoView()方法

scrollIntoView方法可以在所有HTML元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中。如果这个方法传入true作为参数,或者不传参数,那么窗口滚动之后就会让调用元素的顶部与视口顶部 尽可能平齐,如果传入false,调用元素会尽可能全部出现在视口中,不过顶部不一定平齐。

// 让元素可见
document.form[0].scrollIntoView();

专有扩展

插入文本

  • innerText属性
  • outerText属性

滚动

  • scrollIntoViewIfNeeded(alignCenter):只有在当前元素不可见的情况下,才滚动浏览器或窗口或容器元素最终让它可见。如果当前元素在视口中可见,这个方法什么也不做。
  • scrollByLines(lineCount):将元素的内容滚动指定的行高,lineCount值可以是正值,也可以是负值。
  • scrollByPages(pageCount):将元素的内容滚动指定的页面高度,具体高度由元素的高度决定。

scrollIntoView()scrollIntoViewIfNeeded()的作用对象是元素的容器,而scrollByLines()scrollByPages()影响的则是元素自身。

//在当前元素不可见的时候,让它进入浏览器的视口
document.images[0].scrollIntoViewIfNeeded();

//将页面主体往回滚动1 页
document.body.scrollByPages(-1);

DOM2 和 DOM3


样式

元素大小

偏移量

  • offsetHeight:元素在垂直方向上占用的空间大小。包括元素的高度,(可见的)水平滚动条的高度,上边框高度和下边框高度
  • offsetWidth:元素在水平方向上占用的空间大小。包括元素的宽度,(可见的)垂直滚动条的宽度,左边框宽度和右边框宽度
  • offsetLeft:元素的左外边框至包含元素的左内边框之间的像素距离。
  • offsetTop:元素的上外边框至包含元素的上内边框之间的像素距离。

其中,offsetLeftoffsetTop属性与包含元素有关,包含元素的引用保存在offsetParent属性中。offsetParent属性不一定与parentNode的值相等。

// 元素上偏移
function getElementLeft (ele) {
    var actualLeft = ele.offsetLeft;
    var current = ele.offsetParent;

    while (current !== null) {
        actualLeft += current.offsetLeft;
        current = current.offsetParent;
    }
    return actualLeft;
}
// 元素左偏移同理

一般来说,页面中所有的元素都被包含在几个<div>元素中,而这些<div>元素的offsetParent又是<body>元素,所以getElementLeft()getElementTop()会返回与offsetLeftoffsetTop相同的值。

客户区大小

  • clientWidth:元素内容区宽度加上左右内边距宽度
  • clientHeight: 元素内容区高度加上上下内边距高度

function getViewport(){
    if (document.compatMode == "BackCompat"){  // IE7之前
            return {
                width: document.body.clientWidth,
                height: document.body.clientHeight
            };
        } else {
            return {
                width: document.documentElement.clientWidth,
                height: document.documentElement.clientHeight
            };
        }
}

滚动大小

滚动大小,指的是包含滚动内容的元素的大小。有些元素(<html>),即使没有执行任何代码也能自动添加滚动条;但另外一些元素,则需要通过CSSoverflow属性设置才能滚动。

  • scrollHeight:在没有滚动条的情况下,元素内容的总高度
  • scrollWidth:在没有滚动条的情况下,元素内容的总宽度
  • scrollLeft:被隐藏在内容区域左侧的像素数,通过设置这个属性可以改变元素的滚动位置
  • scrollTop:被隐藏在内容区域上方的像素数,通过设置这个属性可以改变元素的滚动位置

scrollWidthscrollHeight主要用于确定元素内容的实际大小。

scrollWidthscrollHeightclientWidthclientHeight 之间的关系?

  • Firefox中这两组属性始终相等,但大小代表的是文档内容区域的实际尺寸,非视口尺寸
  • Opera Safari Chrome中这两组属性有区别,其中scrollWidthscrollHeight 等于视口大小,而clientWidthclientHeight 等于文档内容区域的大小。
  • IE(在标准模式)中的这两组属性不相等,其中scrollWidthscrollHeight 等于文档内容区域的大小,而clientWidthclientHeight 等于视口大小。

通过scrollLeftscrollTop 属性既可以确定元素当前滚动的状态,也可以设置元素的滚动位置。在元素尚未被滚动时,这两个属性的值都等于0。如果元素被垂直滚动了,那么scrollTop 的值会大于0,且表示元素上方不可见内容的像素高度。如果元素被水平滚动了,那么scrollLeft 的值会大于0,且表示元素左侧不可见内容的像素宽度。这两个属性都是可以设置的,因此将元素的scrollLeftscrollTop 设置为0,就可以重置元素的滚动位置。下面这个函数会检测元素是否位于顶部,如果不是就将其回滚到顶部。

function scrollToTop(element){
    if (element.scrollTop != 0){
        element.scrollTop = 0;
    }
}

确定元素大小

  • getBoundingClientRect()方法,会返回一个矩形对象,包含left top right bottom四个属性。这些属性给出了元素在页面中相对于视口的位置。

遍历

NodeIterator

可以使用document.createNodeIterator()方法创建它的新实例,接受4个参数。

  • root:想要作为搜索起点的树中的节点
  • whatToShow:表示要访问哪些节点的数字代码
  • filter:是一个NodeFilter对象,或者一个表示应该接受还是拒绝某种特定节点的函数
  • entityReferenceExpansion:布尔值,表示是否要扩展实体引用。

whatToShow这个参数的值以常量形式在NodeFilter类型中定义:

  • NodeFilter.SHOW_ALL
  • NodeFilter.SHOW_ELEMENT
  • NodeFilter.SHOW_ATTRIBUTE
  • NodeFilter.SHOW_TEXT
  • NodeFilter.SHOW_CDATA_SECTION
  • NodeFilter.SHOW_ENTITY_REFERENCE
  • NodeFilter.SHOW_ENTITYE
  • NodeFilter.SHOW_PROCESSING_INSTRUCTION
  • NodeFilter.SHOW_COMMENT
  • NodeFilter.SHOW_DOCUMENT
  • NodeFilter.SHOW_DOCUMENT_TYPE
  • NodeFilter.SHOW_DOCUMENT_FRAGMENT
  • NodeFilter.SHOW_NOTATION