前端知识点总结——JavaScript

1.script标签的位置

传统的做法是把所有外部文件(CSS文件和JavaScript文件)引用放在head标签内,这意味着必须等到全部JavaScript代码都被下载、解析和执行完以后,才能开始呈现页面的内容(浏览器在遇到body标签时才开始呈现内容),这会导致浏览器在呈现内容时出现延迟,为了避免这个问题,可以将全部JavaScript引用放在body标签中页面内容的后面,这样一来,在解析JavaScript代码之前,页面的内容将完全呈现在浏览器中,带来更好的用户体验。也可以给script标签(只适用于外部文件)设置defer属性(带有defer属性的script标签会被立即下载,但延迟到整个页面都解析完毕后再执行。理论上在DOMContentloaded事件前),或设置async属性(设置async属性的目的在于不让页面等待脚本的下载和执行,从而异步地加载页面其他内容,为此建议异步脚本不要在加载期间修改DOM。一定在onload之前,但不确定在DOMContentloaded事件前)

2.JavaScript中的基本数据类型

Undefined、Null、Boolean、Number、String;

使用typeof操作符可以检测基本数据类型。注意:typeof null会返回object,因为null被认为是一个空的对象引用;对未初始化和未声明的变量使用typeof操作符都会返回undefined;对null和undefined进行相等性操作(==)会返回true;

3.JavaScript中的引用数据类型

引用数据类型包括Object类型、Function类型、Array类型、Date类型、RegExp类型以及基本包装类型。

4.基本类型和引用类型的值

(1)基本类型值时按值访问的,引用类型的值是按引用访问的,操作对象时实际是在操作对象的引用;

(2)从一个变量向另一个变量复制基本类型值时,会为新变量分配位置,并将原变量的值复制过来,两个变量是独立的;当从一个变量向另一个变量复制引用类型值时,也会为新变量分配位置,但复制过来的实际上是一个指针,两个变量将引用同一个对象;

(3)基本类型值存储在栈内存中,而引用数据类型的值存储在堆内存中;

(4)函数的参数都是按值传递的,也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。

5.类型检测

(1)基本数据类型:typeof操作符,注意:typeof null返回“object”;

(2)引用数据类型:instanceof操作符,注意:所有引用类型的值都是Object的实例。

6.作用域及作用域链

作用域定义了变量或函数有权访问的其他数据。每一个作用域都有一个与之关联的变量对象,作用域中定义的所有变量和函数都保存在这个变量对象中。作用域分为两种:全局和局部。在web浏览器中,全局作用域被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的;每个函数都有自己的作用域,即局部作用域。

作用域链,保证了对作用域有权访问的所有变量和函数的有序访问。当代码在一个作用域中执行时,会创建变量对象的作用域链。作用域链的前端,是当前作用域的变量对象,下一个变量对象来自包含环境,一直延续到全局作用域的变量对象。标识符解析就是沿着作用域链一级一级地向上搜索标识符的过程;

JavaScript没有块级作用域;

使用var声明的变量将被自动添加到最近的作用域中,没有使用var声明的变量将被添加到全局作用域中。

7.检测数组

(1)instanceof操作符;

(2)Array.isArray()方法。

8.函数相关

(1)定义函数的方式:函数声明、函数表达式、Function构造函数;

(2)函数名仅仅包含指向函数的指针的变量,与包含对象指针的其他变量一样,故而函数没有重载;

(3)函数声明提升:解析器会率先读取函数声明,并使其在执行任何代码之前可用;而函数表达式则必须等到解析器执行到它所在的代码行;

(4)函数的内部属性:arguments对象、this对象和caller属性

arguments对象是一个类数组,包含着所有传递给函数的参数。arguments对象有一个callee属性,指向拥有这个arguments对象的函数,这可在用到递归算法的函数中应用,可以消除代码与函数名的紧密耦合;

this引用的是函数执行的环境对象。在函数调用前,this的值并不确定。全局作用域中执行时,this引用的是window对象;

函数的caller属性中保存着调用当前函数的函数的引用;

9.函数的apply()方法、call()方法和bind()方法

apply()和call()方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值,区别仅在于接收参数的方式不同。它们接收的第一个参数是特定的作用域,而apply方法的第二个参数可以是arguments对象,也可以是Array的实例,call方法则必须将其余参数直接传递给函数。apply和call方法最重要的是可以扩充函数的作用域,这样对象便不需要和方法有任何的耦合关系;

bind()方法会创建一个函数的实例,其this值会被绑定到传给bind()的参数,即使创建的函数实例是在全局作用域中调用的。

10.基本包装类型:Boolean、Number、String

当读取一个基本类型值时,后台会创建一个对应的基本包装类型的对象,从而可以调用方法来操作数据,操作完成后,便会销毁这个实例。也就是说,自动创建的基本包装类型的对象,只存在于一行代码执行的瞬间,这意味不能在运行时给基本类型值添加任何属性和方法。

String类型的方法:字符方法:charAt()、charCodeAt();字符串操作方法:concat()拼接一个或多个字符串(实践中更多用“+”加号操作符)、slice()方法、subtr()方法、substring()方法;字符串位置方法:indexOf()、lastIndexOf();删除前置或后缀的空格的方法:trim()方法;字符串大小写转换方法:toLowerCase()、toUpperCase()、toLocalLowerCase()、toLocalUpperCase();字符串模式匹配方法:match()、search()、replace()、split()、字符串比较方法:localCompare();编码转换方法:fromCharCode()。

11.单体内置对象:Global和Math

(1)Global对象:URI编码和解码方法:encodeURI()、encodeURIComponent()和decodeURI()、decodeURIComponent();

(2)eval()方法:像是一个完整的JavaScript解释器,只接受 一个参数,即要执行的JavaScript代码。通过eval()执行的代码被认为是包含该次调用的作用域的一部分,因此被执行的代码具有与该作用域相同的作用域链,这意味着通过eval()执行的代码可以引用在包含环境中定义的变量。在eval中创建的任何变量或函数都不会被提升。严格模式下,在外部访问不到eval()中创建的任何变量或函数。应该避免使用eval,不安全且耗性能(解析字符串,然后再执行)。

12.属性类型:数据属性和访问器属性

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

[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者把数据属性修改为访问器属性。直接定义在对象上的属性,这个特性的默认值为true;

[[Enumberable]]:表示能否通过for-in循环返回属性。直接定义在对象上的属性,这个特性的默认值为true;

[[Writable]]:表示能否修改属性的值。直接定义在对象上的属性,这个特性的默认值为true;

[[Value]]:包含这个属性的数据值。读取属性值时,从这个位置读取;写入属性值时,把新值保存在这个位置。

要修改属性默认的特性,必须使用Object.defineProperty()方法。这个方法接收三个参数:属性所在的对象、属性的名字、描述对象,描述对象的属性必须是configurable、enumberable、writable和value。

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

[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者把访问器属性修改为数据属性。

[[Enumberable]]:表示能否通过for-in循环返回属性。

[[Get]]:在读取属性时调用的函数;

[[Set]]:在写入属性时调用的函数;

访问器属性不能直接定义,必须使用Object.defineProperty()来定义,若不指定configurable和enumerable,则默认为false。

可以使用Object.defineProperties来一次定义多个数据属性或访问器属性。

可以使用Object.getOwnPropertyDescriptor()来取得给定属性的特性描述符对象,从而读取属性的特性。

13.创建对象的方式

(1)使用对象字面量方式。

(2)工厂模式:抽象了创建具体对象的过程,用函数来封装以特定接口创建对象的细节。但存在对象识别问题(即怎样知道一个对象的类型)。

(3)构造函数模式:构造函数可以用来创建特定类型的对象,如Object、Array等原生构造函数,运行时会自动出现在环境中。也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。与工厂模式相比,有以下不同之处:没有显式地创建对象;直接将属性和方法赋值给this对象;没有return语句;

定义好构造函数后,使用构造函数创建新实例时,必须使用new操作符,以这种方式调用构造函数会经历以下四个步骤:a、创建一个新对象;b、将构造函数的作用域赋给这个对象(因此this就指向了这个新对象);c、执行构造函数中的代码(为这个新对象添加属性和方法);d、返回新对象;

创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,这正是构造函数模式优于工厂模式的地方。

注意:任何函数,只要通过new操作符来调用,那它就可以作为构造函数;任何函数,如果不通过new操作符来调用,那就是普通函数。高级程序设计上演示了在不同的作用域下调用的示例(普通函数)。

构造函数模式的问题:每个方法都要在每个实例上重新创建一遍,不同实例上的同名函数是不相等的。因为函数也是对象,每定义一个函数,就是实例化了一个对象。但这实在没有必要,可以把函数定义在构造函数之外便能优化这个问题,但这样做的话,如果对象需要定义多个方法,那么就要定义多个外部函数,如此自定义的引用类型便丝毫没有封装性可言,好在这些问题可以通过原型模式来解决。

(4)原型模式

在构造函数的原型对象上添加的属性和方法,被由构造函数创建的所有实例所共享。创建原型对象时,使用对象字面量则更加简洁,但这时,原型对象的constructor属性已经不再指向自定义构造函数了,而是指向Object构造函数了,尽管instanceof操作符仍能返回正确的结果,但constructor属性已经无法确定对象类型了。在对象字面量中显式地设置constructor属性的值则仍可以确保constructor属性返回正确的值。但这种方式重设的constructor属性是可枚举的(默认情况下,原生的constructor属性时不可枚举的),此时可以使用Object.defineProperty()方法。

原型中所有属性被很多实例共享,这种共享对于函数非常合适。但原型模式也存在问题,所有的实例都将取得相同的的属性,而且当原型中的属性值是引用类型值时,通过一个实例修改的属性也会在其他实例中体现出来,所以很少有单独使用原型来创建对象的情况。

(5)组合使用构造函数模式和原型模式的混成模式

混成模式下,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。混成模式是ECMAScript中定义引用类型的默认模式。

(6)动态原型模式

混成模式下,构造函数和原型是单独定义的。动态原型模式把所有信息都封装在鼓噪函数中,必要情况下,在构造函数内部初始化原型,保持了同时使用构造函数和原型的优点。

(7)寄生构造函数模式

函数声明部分的内部代码与工厂模式相同(即创建新对象,根据参数添加属性和方法或执行其他操作,返回新对象),但调用方法与构造函数模式相同(即使用new操作符来调用)。

构造函数默认情况下返回新对象实例,而通过在构造函数尾部添加return语句,可以重写调用构造函数时返回的对象。

注意,寄生构造函数模式返回的对象与构造函数和函数的原型对象之间没有关系,不能依赖instanceof操作符来确定对象类型 。

(8)稳妥构造函数模式

稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象 。稳妥构造函数模式中,创建新对象的实例方法不引用this,而且不使用new操作符来调用。创建的稳妥对象,除了调用添加的方法外,没有别的办法可以访问其数据成员。之后即使可以给稳妥对象添加方法和数据成员,但也不可能有别的办法来访问传入到构造函数中的原始数据。

注意,稳妥构造函数模式返回的对象与构造函数和函数的原型对象之间没有关系,不能依赖instanceof操作符来确定对象类型 。

14.原型对象

每一个函数都有一个prototype属性,这个属性是一个指针,指向函数的原型对象,原型对象的用途在于包含由特定类型的所有实例所共享的属性和方法。默认情况下,原型对象都有一个constructor属性,这个属性指向prototype属性所在的函数。当调用构造函数来创建一个新实例后,新实例内部将包含一个[[Prototype]]属性,这个属性指向构造函数的原型对象,虽然无法访问[[Prototype]],但可以通过isPrototypeOf()来确定原型对象和实例之间的这种关系。ECMAScript5添加了一个方法,Object.getPrototypeOf(实例),这个方法的返回值指向对应的原型对象。

读取对象属性时,会执行一次搜索。搜索首先从对象实例本身开始,如果找到则返回,如果没有找到,则继续搜索[[Prototype]]属性指向的原型对象,这便是所有对象实例共享原型所保存的属性和方法的原理。

虽然可以通过对象实例来访问保存到原型中的值,但却不能通过对象实例来重写原型中的值。如果在对象实例上添加了一个属性,该属性与原型对象上的属性同名,那么就只会在对象实例上创建该属性,而屏蔽掉原型中的对应同名属性(即使将在实例上将属性值设置为null)。使用delete操作符则可以完全删除实例属性,从而能够重新访问原型对象中的属性。

原型具有动态性,对原型对象所做的任何修改都会立即在实例上反应出来——即使是先创建实例后修改原型也是如此,其原因可以归结为实例和原型之间的松散耦合关系。但如果是重写了原型对象则不然,档把原型对象重写为另一个对象时,则先创建的实例中的[[Prototype]]属性指向最初的原型对象,重写后创建的实例中的[[Prototype]]属性则指向新的原型对象。重写原型对象切断了现有原型与任何之前存在的对象实例之间的联系。

也可以在原生对象的原型上添加属性和方法,但并不推荐。

15.原型链

原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。每一个实例都有一个指向其原型对象的内部指针,当让原型对象等于另一个类型的实例时,原型对象将包含指向另一个原型的指针,相应的,另一个原型中也包含着一个指向另一个构造函数的指针。如果另一个原型对象又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是原型链的基本概念。

另外,所有的引用类型都继承自Object,而这个继承也是通过原型链来实现的。切记,所有函数的默认原型都是Object的实例,因此默认原型都会包含指向Object.prototype的内部指针[[Prototype]]。这也正是所有自定义类型都继承了toString()、toLocalString()等存在于Object.prototype中的默认方法的根本原因。

确定原型和实例之间的关系,有两种办法:instanceof操作符和isPrototypeof()方法。对instanceof操作符而言,只要是存在于原型链上出现过的的构造函数,结果都会返回true;同样对isPrototypeof()方法而言,只要是原型链中出现过的原型,也会返回true

16.实现继承的方式

(1)通过原型链实现继承

将一个类型的实例赋给另一个类型的原型对象便实现了继承。注意,作为该类型实例的另一个类型的原型对象中的consturctor属性被重写了。在通过原型链实现继承的情况下,读取属性便会先搜索实例,然后搜索原型对象,然后沿着原型链继续向上搜索,直到原型链的末端。

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

原型链的第一个问题来自于包含引用类型值的原型。在通过原型来实现继承时,原型实际上变成了另一个类型的实例,于是,原先的实例属性变成了现在的原型属性了。通过一个实例对包含引用类型值的原型属性修改会在其他实例中体现出来。

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

鉴于以上两点问题,实践中很少单独使用原型链。

(2)借用构造函数

可以使用借用构造函数技术(constructor stealing,有时也叫作伪造对象或经典继承)来解决原型中包含引用类型值的问题,其基本思想很简单,即在子类型构造函数的内部调用超类型构造函数。函数是在特定环境中执行代码的对象,因此通过使用call或apply方法也可以在将来新创建的对象上执行超类型构造函数。

相对于原型链而言,借用构造函数的最大优点就是可以在子类型构造函数中向超类型构造函数传递参数。在调用了超类型的构造函数之后,也可再添加应该在子类型中定义的属性。

借用构造函数的问题在于,方法都在构造函数中定义,函数复用性降低。而且,在超类型的原型中定义的方法,对于子类型而言也是不可见的。因此也很少单独使用借用构造函数技术。

(3)组合继承(伪经典继承)

将原型链和借用构造函数技术组合使用,其思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。

(4)原型式继承

没有使用严格意义上的构造函数,借助原型可以基于已有的对象创建新实例,同时还不必因此创建新类型。在object函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为构造函数的原型,最后返回这个临时类型的实例。

function object(o){
   function F();
   F.prototype=o;
   return new F();
}

ECMAScript新增加了Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与object()方法的行为相同。Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式定义的任何属性都会覆盖原型对上的同名属性。

注意:原型式继承中,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。

(5)寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路,在函数内部以某种方式来增强对象(如添加方法),最后返回对象。

function createAnother(original){
   var clone=Object.create(original);
   clone.sayHi=function(){
      alert(“Hi”);
   };
   return clone;
}

注意,寄生式继承模式中使用的object()函数不是必须的,任何能够基于已有对象返回新对象的函数都适于此模式。

(6)寄生组合式继承

组合继承(原型链与借用构造函数组合)的最大问题在于,无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另外一次是在子类型构造函数内部。这样带来的后果是:创建子类型原型的时候,子类型原型将包含所有超类型实例属性,调用子类型构造函数创建子类型实例的时候,子类型实例也将包含同名的实例属性,从而屏蔽掉原型上相应属性。解决这个问题的方法——寄生组合式继承。其基本模式如下:

function inheritPrototype(subType,superType){
   var prototype=Object.create(superType.prototype);
   prototype.constructor=subType;
   subType.prototype=prototype;
}

如此,在创建子类型实例时便只调用了一次超类型构造函数,并且避免了在子类型的原型上创建不必要的多余的属性。与此同时,原型链还能保持不变。寄生组合式继承是引用类型最理想的继承方式。

17.闭包

闭包是指有权访问另一个函数作用域中的变量的函数。

创建闭包的常见方式,就是在一个函数内部创建另一个函数。

当函数被调用的时候,会创建一个执行环境及相应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象。作用域链的前端,是当前执行环境的变量对象(对函数而言是其活动对象),包含环境的变量对象排在第二位......直至作为作用域链终点的全局执行环境的变量对象。定义(声明)函数的时候,会预先为函数创建一个包含全局变量对象的作用域链,调用函数的时候,会将函数的活动对象添加到作用域链的前端。作用域链的本质是一个指向变量对象的指针列表,只引用但不实际包含变量对象。全局执行环境的变量对象始终存在,而局部执行环境(函数)的变量对象(即函数的活动对象)仅在函数执行的过程中存在,函数执行完毕后,其活动对象被销毁,内存中仅保存全局执行环境的变量对象。但闭包的情况又有所不同。

在一个函数内部定义函数的时候,就会将包含函数的活动对象及全局变量对象添加到它的作用域链中。当匿名函数从包含函数中被返回后,它的作用域链就被初始化为添加了包含函数的活动对象和全局变量对象。这样匿名函数就可以访问在包含函数中定义的所有变量。而且,包含函数执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍在引用这个活动对象。销毁的是包含函数的作用域链。直到匿名函数被销毁后,包含函数的活动对象才会被销毁。

闭包的缺点:由于闭包会携带包含函数的作用域,因此它比其他函数占用更多内存,过度使用闭包会导致内存占用过多。

另外,由于作用域链的配置机制,闭包只能取得包含函数中任何变量的最后一个值,因为闭包中保存的是整个包含函数的变量对象,而不是某个特殊的变量。

在闭包中使用this对象可能会导致一些问题。我们知道,this对象指向执行函数的环境对象:在全局环境中执行函数,this指向window,而当函数被用作某个对象的方法调用时,this等于该对象。匿名函数的执行环境具有全局性,因此其this对象通常指向window,但有时候由于编写闭包的方式的不同,这一点可能不会那么明显。之前曾经提到过,每个函数在被调用时都会自动取得两个特殊变量:this和arguments,保存于其活动对象上。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数活动对象中的这两个变量。示例如下:


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

为此,可以把外部作用域中this对象保存到一个闭包能够访问的变量中,就可以让闭包访问该对象了。同理,arguments对象也存在同样的问题,也可以通过把外部函数的arguments对象保存到另一个闭包能够访问到的变量中来解决。

18.使用立即执行匿名函数模仿块级作用域(私有作用域)

用作块级作用域的匿名函数的语法如下:

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

私有作用域中的任何变量都会在执行结束后销毁。外部没有办法访问私有作用域中的局部变量,但在私有作用域中能够访问包含作用域中的所有变量。

19.私有变量

任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量,私有变量包括函数的参数,局部变量和在函数内部定义的其他函数。可以通过在函数内部创建闭包来访问私有变量,利用这一点,就可以创建访问私有变量的公有方法。我们把有权访问所有私有变量和私有函数的公有方法称为特权方法,有两种在对象上创建特权方法的方式:

第一种是在构造函数中定义特权方法,特权方法作为闭包有权访问在构造函数中的定义的所有变量和函数,该方法的缺点是必须使用构造函数模式来达到这个目的,每个实例都会创建同样一组新的方法。

第二种方法是通过静态私有变量来实现特权方法,即在私有作用域中定义私有变量或函数,并创建特权方法。注意,构造函数没有使用函数声明,因为函数声明只能创建局部函数,而且,也没有在声明构造函数时使用var关键字,这样,引用构造函数的变量便成为了全局变量,能够在私有作用域之外访问到。另外,公有方法是在构造函数的原型上定义的。

上面两种方法都是为自定义类型创建私有变量和特权方法的。

模块模式:为单例创建私有变量和特权方法。单例,是指只有一个实例的对象。JavaScript以对象字面量的方式来创建单例。创建单例的语法形式:使用返回对象的匿名函数,并立即执行。匿名函数内部,定义了私有变量和函数。返回的对象上定义了公开的属性和方法。

20.setTimeout()和setInterval()

这两个方法的第一个参数为要执行的代码字符串或函数,第二个参数是等待时间。由于JavaScript是一个单线程的解释器,因此一定时间内执行一段代码。为了控制要执行的代码,会创建一个JavaScript任务队列,任务会按照添加顺序来执行。第二个参数是高速JavaScript经过多长时间把任务添加到队列中。如果队列是空的,则立即执行。如果队列不是空的,则要等到前面的代码执行完成后在执行。

setTimeout()和setInterval()中的代码都是在全局作用域中执行的,因此函数中的this值指向window。

21.DOM相关

DOM中共有12种节点类型,而所有类型都继承自Node类型,因此所有节点类型共享相同的基本属性和方法(如nodeType、nodeName、nodeValue、childNodes(含空白符节点)、parentNode、previousSibling、nextSibling、firstChild、lastChild、hasChildNodes()方法、ownerDocument)

操作节点的方法有:操作子节点:appendChild()、insertBefore()、replaceChild()、removeChild();其他:cloneNode()、normalize()。

(1)Document类型:document文档对象时Document类型的实例。document.documentElement指向HTML元素,document.body指向body元素,document.title为title元素中的文本。document.URL包含页面完整URL、document.domain包含页面的域名、document.referrer包含着链接到当前页面的那个页面的URL。取得特定元素的方法有document.getElementById()、document.getElementsByTagName()、document.getElementByName()。DOM一致性检测方法:document.implementation.hasFeature()。文档写入方法:document.write()、document.writeIn()。

(2)Element类型

DOM对象的id、title、className属性对应于相应HTML元素的特性。操作特性的方法有:getAttribute()、setAttribute()和removeAttribute()。注意:只有公认的特性才会以属性的形式添加到DOM对象中。通常使用对象属性的方式来操作特性,只有操作自定义特性的时候才会用后面三种方法。自定义的元素特性不会成为对象的属性,给对象添加的自定义属性也不会成为元素的特性。

DOM对象的attributes属性:返回类数组对象。元素的每一个特性都由一个特性节点Attr表示,保存在对应DOM元素的attributes属性中。

创建元素:document.createElement()。

(3)Text类型

创建文本节点:document.createTextNode()。

(4)DocumentFragment类型创建文档片段:document.createDocumentFragment

(5)Attr类型

创建特性节点:document.createAttribute();为元素设置特性节点:setAttributeNode();获取特性节点:getAttributeNode();

Attr对象的属性:name、value、specified。

注意:DOM操作是JavaScript中开销最大的部分,尤其是访问NodeList导致的问题更为突出。因为NodeList是“动态的”,因此每次访问NodeList对象都会运行一次查询。所以应该尽量减少DOM操作。

22.DOM扩展

(1)选择符API:querySelector()和querySelectorAll()。

 (2)元素遍历:childElementCount、firstElementChild、lastElementChild、previousElementSibling、nextElementSibling。

(3)与类相关的扩充:getElementsByClassName()方法;classList属性:add(value)、contains(value)、remove(value)、toggle(value)。

(4)焦点管理:document.activeElement引用DOM中当前获得了焦点的元素。focus()方法。document.hasFocus()方法。

(5)document.readyState属性有两个可能的值:loading表示正在加载文档,complete表示已经加载完文档;

document.compatMode检测文档的渲染模式是标准模式还是混杂模式;

document.head引用文档的head元素。

(6)插入标记:innerHTML属性、outerHTML属性、insertAdjacentHTML()。

(7)页面滚动:scrollIntoView()方法。

(8)专有扩展:children属性(不含空白符)、contains()方法、compareDocumentPosition()方法、插入文本:innerText属性和outerText属性。

23.样式相关

(1)访问元素内部样式:任何支持style特性的HTML元素在JavaScript中都有一个对应的style属性,这个对象是CSSStyleDeclaration的实例,包含着通过HTML特性指定的样式信息,但不包含与外部样式表或内部样式表经层叠而来的信息。在style特性中指定的任何css属性都将表现为这个style对象的属性(属性名采取驼峰书写方法)。

DOM样式的属性和方法:cssText、length、getPropertyPriority(propertyName)、getPropertyValue(propertyName)、item(index)、removeProperty(propertyName)、setProperty(propertyName,value,priority)。

(2)计算样式:document.defaultView.getComputedStyle()方法,返回CSSStyleDeclaration对象(与DOM元素style属性返回的类型相同,具有类似的属性和方法),其中包含当前元素的所有计算样式。IE中通过DOM元素的currentStyle属性来获取元素计算后的样式。注意:计算样式是只读的,不能修改计算样式对象中的css属性。

(3)操作样式表(包括内部样式表和外部样式表)

CSSStyleSheet类型表示的是样式表,通过document.styleSheets集合来取得所有应用于文档的样式表,通过方括号语法或item()方法来访问每一个样式表。CSSStyleSheet类型具有下列属性:disabled、href、media、ownerNode、title、type、cssRules(IE中为rules)、deleteRule(index)(IE中为removeRule())、insertRule(rule,index)(IE中为addRule())。

(4)css规则

获取到样式表后,通过cssRules属性或rules属性(IE中),可以获取到样式表中的规则集合,通过方括号语法或item()方法可以访问每一条css规则。css规则为CSSRule对象类型,每一个CSSRule对象都包含以下属性:cssText、parentStyleSheet、selectorText、style(一个CSSStyleDeclaration对象)。

(5)元素大小相关

偏移量:offsetHeight、offsetWidth、offsetLeft、offsetTop。

客户区大小:clientWidth、clientHeight。

滚动大小:scrollHeight、scrollWidth、scrollLeft、scrollTop。

25.遍历

有两个用于辅助完成深度优先顺序遍历DOM结构的类型:NodeIterator和TreeWalker。任何节点都可以作为遍历的根节点。

(1)NodeIterator:document.createNodeIterator()方法创建一个遍历实例。该方法接收四个参数:根节点、要遍历的节点类型、过滤函数和false。该类型有两个主要方法:nextNode()和previousNode(),用于在遍历中前进和后退。

(2)TreeWalker:document.createTreeWalker()方法创建一个TreeWalker实例,除了包括与NodeIterator的nextNode()和previousNode()相同的功能之外,还有以下在不同方向上遍历DOM结构的方法:parentNode()、firstNode()、lastNode()、nextSibling()和previousSibling()。

26.事件流

事件流描述的是从页面中接收事件的顺序。IE的事件流是冒泡流,而Netscape Communictor的事件流是捕获流。

DOM2级事件规定事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。

27.事件冒泡与事件捕获

事件冒泡:事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点。

事件捕获:事件最开始由较为不具体的节点接收,然后逐级向下传播到最具体的元素(文档中嵌套层次最深的那个节点)

28.注册事件处理程序的几种方式

(1)HTML事件处理程序:使用与相应事件处理程序同名的HTML特性来指定。缺点:紧密耦合。

(2)DOM0级事件处理程序:将函数赋值给一个元素的事件处理程序属性。使用DOM0级方法指定的事件处理程序被认为是元素的方法,因此事件处理程序是在元素的作用域中运行的,事件处理程序中的this引用当前元素。DOM0级方法指定的事件处理程序在事件冒泡阶段被处理。可以通过将事件处理程序属性设置为null来删除之前指定的事件处理程序。

(3)DOM2级事件处理程序:指定事件处理程序addEventListener();删除事件处理程序removeEventListener()。这两个方法都接收三个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值,若布尔值为true,则表示在捕获阶段调用事件处理程序,若是false则表示在冒泡阶段调用事件处理程序。添加的事件处理程序也是在元素的作用域中运行的。

使用DOM2级方法添加事件处理程序的好处就是可以添加多个事件处理程序,事件处理程序会按照添加他们的顺序触发。

使用addEventListener()添加的事件处理程序只能使用removeEventListener()来移除;移除是传入的参数与添加时传入的参数相同。所以,通过匿名函数添加的事件处理程序将无法移除,而必须使用函数名。

通常将事件处理程序添加到冒泡阶段,可以最大限度的兼容各种浏览器。除非必须在事件到达目标元素之前截获它时将事件处理程序添加到捕获阶段。

29.IE事件处理程序

为元素指定事件处理程序attachEvent();删除事件处理程序detachEvent()。这两个方法接收相同的两个参数:事件处理程序名称(带前缀)和事件处理函数。通过attachEvent()添加的事件处理程序在冒泡阶段调用。attachEvent()也可以为一个元素添加多个相同的事件,但事件处理程序按照与添加顺序相反的顺序触发。注意:attachEvent()添加的事件处理程序在全局作用域中运行,因此this指向window。同理,匿名函数无法移除,必须传入与添加时相同的函数名才可以移除事件。

30.事件对象

(1)DOM中的事件对象:无论使用DOM0级还是DOM2级方法,都会传入event对象。所有事件event对象都有下列属性和方法:bubbles、cancelable、currentTarget、eventPhase、preventDefault()、stopImmediatePropagation()、stopPropagation()、target、type。

要阻止事件默认的行为,可以使用event.preventDefault()方法;而event.stopPropagation()方法则用来阻止事件在DOM中的传播,即进一步取消捕获或冒泡。

(2)IE中的事件对象:访问IE中的event对象的方式取决于指定事件处理程序的方式。如果使用DOM0级方法添加事件处理程序,则event对象作为window对象的属性;如果使用attachEvent()方法添加事件处理程序,则event对象作为参数传入事件处理程序函数中,此时也可通过window.event来取得event对象。所有event对象都具有下列属性和方法:cancelBubble、returnValue、srcElement和type。将cancelBubble设置为true就可以取消冒泡;将returnValue设置为false就可以取消默认行为;srcElement为事件的目标元素。

31.currentTarget和target的区别

在事件处理程序内部,this始终等于currentTarget的值,都指向绑定事件处理程序的那个对象;而target指向事件的实际目标(即事件发生时最具体的那个元素)。

32.编写跨浏览器的事件处理程序

var EventUtil={
	addHandler:function(element,type,handler){
		if(element.addEventListener){
			element.addEventListener(type,handler,false);
		}else if(element.attachEvent){
			element.attachEvent("on"+type,handler)
		}else{
			element["on"+type]=handler;
		}
	},
	removeHandler:function(element,type,handler){
		if(element.removeEventListener){
			element.removeEventListener(type,handler,false);
		}else if(element.detachEvent){
			element.detachEvent("on"+type,handler);
		}else{
			element["on"+type].null;
		}
	},
	//使用该对象时,在事件处理函数内部,首先获取到事件对象,然后在获取目标元素或者取消冒泡或者阻止默认行为
	getEvent:function(event){
		return event?event:window.event;
	},
	getTarget:function(event){
		return event.target||event.srcElement;
	},
	preventDefault:function(event){
		if(event.preventDefault){
			event.preventDefault;
		}else{
			event.returnValue=false;
		}
	},
	stopPropagation:function(event){
		if(event.stopPropagation){
			event.stopPropagation;
		}else{
			event.cancelBubble=true;
		}
	}
}

33.load事件与DOMContentLoaded事件

当页面完全加载后,包括所有图像、JavaScript文件、css文件等外部资源全部下载之后,就会触发window上面的load事件。DOMContentLoaded事件是在形成完整的DOM树之后就会触发,而不会等到外部资源加载完毕。

34.事件相关的内存与性能

添加到页面中的事件 处理程序过多就会影响到页面的整体运行性能。原因在于:一方面,每个函数都是对象,都会占用内存,内存中的对象越多性能就越差;另一方面,指定过多的事件处理程序就要过多的访问DOM。使用事件委托可以提高性能。

事件委托利用事件冒泡,在DOM树种尽量最高的层次上只指定一个事件处理程序,就可以管理某一类型的所有事件。

另外,应该适当的移除事件处理程序。当从页面中移除带有事件处理程序的元素时,添加到元素中的事件处理程序极有可能无法被当做垃圾回收。所以最好在移除元素之前先移除事件处理程序(使用事件委托也有助于解决这问题 )。

35.Ajax相关

(1)创建XHRHttpRequest对象:new  XMLHTTPRequest()。

(2)XHR对象的相关属性和方法:

open():请求类型、请求URL、是否发送异步请求的布尔值。启动一个请求以备发送。

send():要作为请求主体发送的数据。

readyState属性:请求或者响应的当前活动阶段。

responseText属性:作为响应主体被返回的文本。

responseXML属性:XML DOM文档。

status:响应的http状态。

statusText:http状态的说明。

abort():在接收到响应之前取消异步请求。

setRequestHeader():设置自定义的请求头部信息。必须在open方法之后send方法之前调用该方法。

getResponseHeader():传入头部字段名称可以取得相应的响应头部信息。

getAllResponseHeader():取得包含所有信息的长字符串。

(3)get请求:最常用于向服务器查询某些信息,必要时可以将查询字符串参数追加到URL尾部,以便将信息发送给服务器,注意,查询字符串必须经过正确的编码才行。

post请求:通常用于向服务器发送应该被保存的数据,post请求把数据作为请求的主体提交,且格式不限。

(4)FormData类型:序列化表单以及创建与表单格式形同的数据。可以向new FormData()中传入表单元素,也可以使用append()方法向其中添加键值对儿。最后将数据提交。

(5)超时设定timeout属性:如果在设定时间内没有接收到响应,那么就会触发timeout事件。

(6)overrideMimeType()方法:用于重写XHR响应的MIME类型。调用overrideMimeType()必须在send方法之前,才能保证重写响应的mime类型。

(7)进度事件:loadstart、progress、error、abort、load、loadend。其中load事件用以代替readystatechange事件,响应接收完毕后触发load事件,onload事件处理程序接收到event事件对象,其target属性指向XHR对象。另外,progress事件在浏览器接收数据期间周期性的重复触发,onprogress事件处理程序接收到event对象,其target属性指向XHR对象,另外包含三个额外的属性:lengthComputable、position、totalSize,利用这些属性可以创建一个进度指示器。注意,必须在调用open()方法之前添加onprogress事件处理程序。

36.CORS跨域源资源共享

Ajax受到同源策略的限制,CORS允许浏览器向跨源服务器发出XMLHttpRequest请求。整个CORS通信过程,都由浏览器自动完成,不需要用户参与,对于开发者而言,CORS通信与同源的Ajax请求没有差别,代码完全一样。浏览器一旦发现Ajax请求跨源,就会自动附加一些头部信息,但用户感觉不到。实现CORS通信的关键就是服务器,只要服务器实现了CORS接口,就可以跨源通信。

IE是通过XDR对象来实现CORS的,而其他浏览器可以通过XHR对象实现对CORS的原生支持,使用时在open()方法中传入绝对URL即可。默认情况下,跨源请求不带凭据(cookie、HTTP认证及客户端SSL证明),通过将withCredentials属性设置为true,可以指定某个请求应该发送凭据。

37.其他跨域技术

(1)图像Ping:一个网页可以从任何网页中加载图像,而不用担心是否跨域,故可以通过动态创建图像实现图像Ping。图像Ping是与服务器进行简单、单向的跨域通信的一种技术,并使用onload和onerror事件处理程序来确定是否接收到响应。图像Ping的缺点在于,只能发送get请求,且无法访问服务器的响应文本,因此图像Ping只能用于浏览器和服务器间的单向通信。

(2)JSONP(填充式JSON):JSONP由两部分组成:回调函数和数据回调函数值当响应到来时应该在页面中调用的函数,回调函数的名字一般是在请求中指定的,而数据就是传入回调函数的JSON数据。

JSONP是通过动态script元素来使用的使用时可以为其src属性指定一个跨域URL,script元素有能力不受限制地从其他域中加载资源。当JSONP响应加载到页面中以后,就会立即执行回调函数。与图像Ping相比,JSONP的优点在于可以直接访问响应文本,因此支持双向通信。JSONP的缺点在于一是安全问题,而是要确定JSONP请求是否失败并不容易,为此,不得不使用定时器来检测指定时间内是否接受到响应。

(3)Comet:Ajax是从页面向服务器发送数据的技术,而Comet则是一种服务器向页面推送数据的技术。Comet能够让信息实时地被推送到页面上。有两种实现Comet的方式:长轮询和流。

长轮询是指浏览器定时向服务器发送请求,然后服务器一直保持连接打开,直到有数据可以发送,发送完数据后,浏览器关闭请求,随后又发送一个新的请求至服务器,这一过程在页面打开期间一直持续不断。(相比之下,短轮询是指浏览器定时发送请求,服务器接收到请求后立即返回响应,无论数据是否更新)。轮询的优势在于所有浏览器都支持,使用XHR对象和settimeout()就能实现,所要做的只是决定什么时候发送请求。

流不同于轮询,在它的生命周期内只是用一个http连接。浏览器向服务器发送请求,服务器保持连接打开,然后周期性的向浏览器发送数据。通过侦听readystatechange事件及检测readyState的值是否为3,就可以利用XHR对象实现http流。

38.Web Sockets

Web Sockets在一个单独的持久连接上实现全双工,双向通信。Web Sockets使用了自定义的协议。使用时首先创建一个WebSocket对象,并传入要连接的URL(必须传入绝对URL)。WebSocket对象也有readyState属性,但无readystatechange事件。建立好连接以后,就可以通过连接发送和接收数据——send()方法便可以向服务器发送数据,当浏览器接收到消息时,就会触发Websocket对象的message事件,返回的数据存储在event.data属性中。使用close()方法可以关闭连接。WebSocket对象还有三个事件,在连接生命周期的不同阶段触发:open、error、close。

39.惰性载入函数

由于浏览器之间的差异,多数JavaScript代码包含了大量的if语句。如果if语句不必每次执行,那么代码可以运行地更快一些,解决方案就是采用惰性载入函数技巧。惰性载入函数表示函数执行的分支仅会发生一次。

有两种实现惰性载入函数的技巧,第一种就是在函数被调用时在处理函数。在第一次调用的过程中,该函数会被覆盖为另外一种按合适方式执行的函数,这样对原函数的再次调用都不会再经过执行的分支了。第二种实现惰性载入的方式就是在声明函数时就指定适当的函数。

惰性载入函数的优点在于只在执行分支代码时牺牲一点性能,避免执行不必要的代码。

40.函数绑定

在特定的作用域下执行函数。一个简单的bind()函数接收一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动地传递进去。语法如下:

function bind(fn,context){
	return function(){
		return fn.apply(context,arguments)
	};
}

ECMAScript5为所有函数定义了一个原生的bind()方法,进一步简化了操作,可以直接在函数上调用这个方法。

41.函数柯里化(function currying)

函数柯里化:用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法与函数绑定是一样的:使用一个闭包返回一个函数,并且当函数被调用时,返回的函数还需要设置一些参数。

柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。以下是创建柯里化函数的通用方式:

function curry(fn){
	var args=Array.prototype.slice.call(arguments,1);              //args包含了来自外部函数的参数(从第二个往后)
	return function(){
		var innerArgs=Array.prototype.slice.call(arguments);   //innerArgs包含了来自内部函数的所有参数
		var finalArgs=args.concat(innerArgs);                  //将args与innerArgs组合为一个参数数组
		return fn.apply(null,finalArgs);                       //调用要柯里化的函数,并传入finalArgs
	}
}

函数柯里化还常常作为函数绑定的一部分包含在其中,构造出更为复杂的bind()函数。如下:

function bind(fn,context){
	var args=Array.prototype.slice.call(arguments,2);
	return function(){
		var innerArgs=Array.prototype.slice.call(arguments);
		var finalArgs=args.concat(innerArgs);
		return fn.apply(context,finalArgs);
	}
}

ECMAScript5的bind()方法也实现了函数柯里化,只要在传入的this值之后再传入另一个参数即可。

42.防篡改对象

ECMAScript5添加了几种方法,通过它们可以指定对象的行为。注意,一旦把对象定义为防篡改,就无法撤销了。

(1)不可扩展对象:Object.preventExtensions(),传入一个对象,则不能给这个对象添加属性和方法。但是已有属性和方法不会受到任何影响,仍可以修改和删除已有的成员。另外,使用Object.isExtensible()方法可以确定对象是否可扩展。

(2)密封的对象:Object.seal(),传入一个对象,密封对象不可扩展,而且已有成员的[[Configurable]]特性将被设置为false,这意味着不能删除属性和方法,属性值是可以修改的。另外,使用Object.isSealed()方法可以确定对象是否被密封。

(3)冻结的对象:Object.freeze(),既不可扩展,又是密封的,而且对象数据属性的[[Writable]]特性被设置为false,即不可以修改数据属性。

43.垃圾收集

JavaScript具有自动收集垃圾机制,原理很简单:找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔周期性地执行这一操作。用于标识无用变量的策略有两种:标记清除和引用计数。

(1)标记清除:JavaScript中最常用的垃圾收集方式就是标记清除。当变量进入环境时,就将这个变量标记为“进入环境”,永远不能释放进入环境的变量所占用的内存。当变量离开环境时,则将其标记为“离开环境”。

垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后,他会去掉环境中的变量以及被环境中的变量引用的变量的标记。在此之后再被加上标记的变量便被视为准备删除的变量,因为环境中的变量已经无法访问这些变量了。最后,垃圾收集器完成内存清理工作,销毁那些带标记的值并收回他们所占用的内存。

目前,大多数浏览器的JavaScript实现使用的都是标记清除式的垃圾收集策略,只是垃圾收集的时间间隔互有不同。

(2)引用计数:跟踪记录每个值被引用的次数,当声明了一个变量并将一个引用类型值赋给该变量时,这个值的引用次数就是1.如果同一个值又被赋给另外一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1.当这个值的引用次数变成0时,则说明这个没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。

引用计数法存在一个问题——循环引用,即两个对象都包含指向对方的指针,这样两个对象的引用次数便都是2,对象的引用次数永远不会是0,因此对象所占用的内存永远不会被回收。

44.管理内存

优化内存占用的最佳方式,就是为执行中的代码值保存必要的数据,一旦数据不再有用,便将其值设置为null来释放其引用——解除引用,垃圾收集器会在下次运行时将其回收。该方法适于大多数全局变量和全局对象的属性,局部变量会在他们离开执行环境时自动被解除引用。

45.var声明及变量提升机制

在函数作用域或者在全局作用域中通过关键字var声明的变量,无论实际上是在哪里声明的,都会被当成在当前作用域顶部声明的变量,这就是变量提升机制。








你可能感兴趣的:(前端知识点总结——JavaScript)