ECMA-262定义对象:无序属性的集合,其属性能够包含基本值,对象或者函数。
普通理解:对象是一组没有特定顺序的值。
对象的每一个属性或方法都有一个名字,而每一个名字都映射一个值。javascript
每一个对象都是基于一个引用类型建立的。java
属性类型正则表达式
在定义只有内部的特征(attribute)时,描述了属性(property)的各类特征。
是为了实现JavaScript引擎用的,在JavaScript中不能直接访问他们。
为了表示特性是内部值,把它们放在了两对方括号中. 例如: [[Enumerable]]segmentfault
ECMAScript中有两种属性:数据属性和访问器属性。数组
数据属性
数据属性包含一个数据值的位置。在这个位置能够读取和写入值。数据属性有4个描述行为的特性
[[Configurable]]
: 可否经过delete删除属性从而从新定义属性,可以修改属性的特性,或者可否把属性修改成访问属性。 默认值:false;(不能够从新定义或删除)[[Enmuerable]]
: 可以经过for-in循环返回属性。(是否能够被枚举).默认值:false;(不能够被枚举)[[Writable]]
: 可否修改属性的值。默认值:false,(不能够被修改)[[Value]]
:包含这个属性的数据值,读取属性的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。默认值:undefiend.var Person = { name: 'Nicholas' } // 建立name属性。为它置顶的值是“Nicholas”。也就是[[Value]]被设置了"Nicholas",而对这个值的任何修改都将反映在这个位置。
Object.defineOProperty();
做用:修改属性默认特性
参数1:属性所在的对象
参数2:属性的名字
参数3:一个描述符对象
描述符(descriptor)对象的属性必须是:configurable,enumerable,writable,value.
设置其中一个或多个值,能够修改对应的特性值。浏览器
var Person = {}; Object.defineProperty(Person, "name", { writable: false, value: "Nicholas" }); alert(Person.name); //"Nicholas" Person.name = "Greg"; // 设置成只读的,属性是不可修改的。 alert(Person.name); //"Nicholas"
constructor属性,是没法被枚举的. 正常的for-in循环是没法枚举. [eable = false];缓存
Object.getOwnPropertyNames(); //枚举对象全部的属性:无论该内部属性可以被枚举.安全
//3个参数, 参数1:从新设置构造的对象 (给什么对象设置) 参数2:设置什么属性 参数3:options配置项 (要怎么去设置) Object.defineProperty(Person.prototype,'constructor',{ enumerable: false, //是不是 可以 被枚举 value: Person //值 构造器的 引用 });
访问器属性
访问器属性不包含数据值,包含一堆geter() 和setter(); 函数 (这两个函数都不是必须的)
在读取访问器属性时,会调用getter(); 这个函数负责返回有效值,在写入访问器属性时,会调用setter()函数并传入新值,这个函数负责决定如何处理数据。
访问器属性的4个特性:
[[Configurable]]
: 可以经过delete删除属性从而定义新的属性,可否修改属性的特性,或者可否把属性修改成数据属性。默认值:false; (不可从新定义,和删除属性)[[Enmuerable]]
:可否经过for-in循环返回属性。默认值:false;(不可被枚举)[[Get]]
: 在读取属性时调用的函数。默认值为:undefeind[[Set]]
: 在写入属性时调用的函数。默认值为:undefiend访问器属性不能直接定义,必须使用Object.defineProperty();来定义。服务器
var book = { _year: 2016, edition: 1 }; Object.defineProperty(book, "year", { get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2016) { this._year = newValue; this.edition += newValue - 2016; } } }); book.year = 2020; alert(book.edition); // 5 // _year前面的下划线是一种经常使用的记号,用于表示只能经过对象方法访问的属性。
定义多个属性 网络
Object.defineProperties();
定义多个属性。
参数1:对象要添加和修改其属性的对象
参数2:对象的属性与第一个对象中要添加或修改的属性一一对应.
var book = {}; Object.defineProperties(book, { _year: { value: 2016 }, edition: { value: 1 }, year: { get: function () { return this._year; }, set: function (newValue) { this._year = newValue; } } });
读取属性的特性
Object.getOwnPropertyDescriptor()
取得给定属性的描述符.
参数1:属性所在的对象
参数2:要读取其描述符的属性名称
返回值:对象。若是是访问器属性,这个对象含有:configurable,enumerable,get和set
若是是数据属性,这个对象含有:configureable,enumerbale,writeable,value
工厂模式
解决:建立多个类似对象。(未解决:对象识别的问题-怎么知道一个对象的类型)
function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var person1 = createPerson("Nicholas", 29, "Software 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");
建立实例会通过:
对象的 constructor 属性最初是用来表示对象类型。
提到检测对象类型,仍是使用instanceof操做符要更可靠。
将构造函数看成函数
构造函数与其它函数的惟一区别,在于调用它们的方式不一样。
构造函数也是函数,不存在定义构造函数的语法。任何函数,只要经过new操做来调用,就能够做为构造函数。
任何函数,若是不经过new操做符来调用,就跟普通函数没什么不一样。
构造函数的问题
每一个方法都要在每一个实例上从新建立
经过原型模式来解决
原型模式
每一个函数都有一个 prototype (原型)属性,这个属性是一个指针,指向一个对象,
而这个对象的用途是包含能够由特定类型的全部实例共享的属性和方法
每一个函数都有一个prototype(原型)属性,这个属性石一个指针,指向一个对象,而这个对象的用途是包含能够由特定类型的全部实例共享的属性和方法。
字面的理解:prototype就是经过调用构造函数而建立的那个对象实例的原型。
让全部对象实例共享它全部包含的属性和方法。没必要在构造函数中定义对象实例的信息,而是将这些信息直接添加到原型中。
理解原型对象
只要建立了一个函数,就会根据一组特定的规则为该函数建立一个prototype属性,这个属性指向函数的原型对象。
默认状况下:全部原型对象都会自动得到一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。
建立了自定义的构造函数以后,其原型对象默认只会取得constructor属性。
当调用构造函数建立一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。
原型链查找:
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具备给定名字的属性。搜索首先从对象实例自己开始。若是在实例中找到了具备给定名字的属性,则返回该属性的值,若是没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具备给定名字的属性。若是在原型对戏那个中找到这属性,则返回该属性的值。
能够经过对象实例访问保存在原型中的值,但却不能经过对象实例重写原型中的值,若是在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那在实例中建立该属性,该属性将会屏蔽原型中的那个属性。
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = "Greg"; alert(person1.name); //"Greg" —— 来自实例的name alert(person2.name); //"Nicholas" -- 来自原型中的name
当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。添加这个属性只会组织访问原型中的那个属性,但不会修改那个属性。即便将这个属性设置为null,也只会在实例中设置这个属性,而不会恢复其指向原型的链接。可使用delete操做符能够彻底删除实例属性。
hasOwnProperty() // 从Object继承而来的方法
检测一个属性是否存在于实例中,仍是存在于原型。
判断一个对象属性 是属于 原型属性 仍是属于 实例属性
在实例中,返回true
在原型上,返回false
function Person() {} Person.prototype.name = 'Nicholas'; var p1 = new Person(); console.log(p1.hasOwnProperty('name')); // false
原型与in操做符
in 操做符,经过对象可以访问给定属性时返回true,不管是在实例中仍是原型中。可以访问到,就返回true.
同时使用hasOwnProperty(); 和 in操做符 : 肯定是属性存在实例中,仍是原型中。
// 判断属于原型上的属性 function hasPrototypeProperty( obj, attr ) { return !obj.hasOwnProperty(attr) && attr in obj; }
Object.keys()
参数:接收一个对象做为参数,返回一个包含全部可枚举的属性的字符串数组。
function Person() {} Person.prototype.name = 'Nicholas'; Person.prototype.age = 20; var p1 = new Person(); var keys = Object.keys(Person.prototype); console.log(keys); // ["name", "age"]
更简单的原型方法
function Person() {} Person.prototype = {}
将Person.prototype设置为等于一个对象字面量形式建立的新对象。
会形成constructro属性再也不指向Person。
每建立一个函数,就会同时建立它的peorotype,这个对象也会自动得到constructro属性。直接赋值为一个字面量形式,本质上是彻底重写了默认的prottoype对象,所以constructor属性也就变成新的对象的constructor属性(指向Object构造函数) 再也不指向Person函数。
经过instanceof操做符可以返回正确的结果,可是经过constructor已经没法肯定对象的类型了。
能够手动设置回constructor属性为当前的类
function Person () {} Person.prototype = { constructor: Person, name: 'Nicholas' }
这种方式重设constructor属性会致使它的[[Enumerable]]特性被设置为true
constructor是不可被枚举,能够经过Object.definePropert();修改特性,重置构造函数。
Object.defineProperty(Person.protype, 'constructor', function () { enmuerable: false, value: Person });
原型动态
因为在原型中查找值的过程是一次搜索,对原型对象所作的任何修改都可以当即从实例上反应出来,即便是先建立了实例后修改原型也是同样。
实例中的指针仅指向原型,而不指向构造函数。
能够随时为原型添加属性和方法,而且修改可以当即在全部对象实例中反映出来,但若是是重写了这个原型对象,那么就不同。
调用构造函数时会为实例添加一个指向最初原型的[[Prototype]] 或者__proto__ .而把原型修改成另一个对象就等于切断了构造函数与最初原型之间的联系。
function Person () {} var firend = new Person(); Person.prototype = { constructor: Person, name: 'Nicholas', sayName: function () { console.log(this.name); } } firend.sayName(); // error // friend 指向的原型中不包含以该名字命名的属性
原生对象的原型
原型模式的体如今建立自定义类型方面,全部的原生的引用类型,都是采用这种模式建立的。
全部元素引用类型(Object,Array,String...)都在其构造函数上定义了方法
经过原生对象的原型,不只能够取得全部默认方法的引用,并且也能够定义新的方法。
console.log( typeof Array.prototype.slice ); // function
不建议这样使用,会致使命名冲突,也可能意外的重写原生的方法。
原型对象的问题
原型模式省略了为构造函数传递参数初始化的,形成全部实例默认状况下都将取得相同的属性值。
原型模式最大的问题是由其共享的本性所致使。
对于包含引用类型的原型对象,若是修改其值,那么另个实例也会修改。
function Person () {} Person.prototype = { constructor: Person, name: 'Nicholas', friends: ['Shelby', 'Court'] } var p1 = new Person(); var p2 = new Person(); p1.friends.push('Van'); console.log(p1.friends); // ["Shelby", "Court", "Van"] console.log(p1.friends); // ["Shelby", "Court", "Van"]
实例通常都要有属性本身的所有属性。这个问题形成不多会单独使用原型模式。
组合使用构造函数模式和原型模式
构造函数模式:用于定义实例的属性
原型模式:用于定义方法和共享的属性。
结果:每一个实例都会有本身的一份实例属性的副本,但同时又共享这对方法的引用,最大限度的节省内存,同时还支持向构造函数传递参数。
是目前在ECMAScript中使用最普遍,认同度最高的一种建立自定义类型的方法。是用来定义引用类型的一种默认模式。
动态原型模式
把信息都封装到函数中,这样体现了封装的概念。
经过在构造函数中初始化原型(仅在必要状况下),同时保持了同时使用构造函数和原型的优势。
能够经过检查默认应该存在的方法是否有效,来决定是否须要初始化运原型。
//动态原型模式:(让你的代码 都封装到一块儿) function Person( name,age,firends ) { this.name = name; this.age = age; this.firends = firends; //动态原型方法 if ( typeof this.sayName !== 'function' ) { Person.prototype.sayName = function () { console.log(this.name); } } }
sayName()方法不存在的状况下,才会将它添加到原型中if语句中的代码只会在初次调用构造函数时才会执行。此后,原型已经完成初始化,不须要再作什么修改。这边的原型所作的修改,可以理解在全部实例中获得反映。
if语句的检查能够是初始化以后应该存在的任何属性或方法---没必要用一大堆if语句检查每一个属性和每一个方法。只要检查其中一个便可。
采这种模式建立的对象,还可使用instanceof操做符肯定它的类型。
使用动态原型模式时,不能使用对象字面量重写原型。若是在已经建立了实例的状况下重写原型,就会切断现有实例与新原型之间的联系。
寄生构函数模式
建立一个函数,该函数的做用仅仅是封装建立对象的代码,而后再返回新建立的对象。
在工厂模式,组合使用构造函数模式和原型模式,都不能使用的状况下,使用。
这个模式与工厂模式是相似的,构造函数在不返回值的状况下,默认会返回新对象的实例。而在构造函数末尾添加一个return语句,能够重写调用构造函数时返回出来的值。
function Person ( name ) { var o = new Object(); o.name = name; o.sayName = function () { console.log(this.name); } return o; } var friend = new Person('Van'); friend.sayName();
寄生构造函数模式,首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系。
构造函数返回的对象与在构造函数外部建立的对象没有什么不一样,为此,不恩呢该依赖 instanceof操做符来肯定对象类型。
通常来讲,不可经过prototype修改原生对象的方法。可使用寄生构造函数模式,达到特殊需求。
稳妥构造函数模式
稳妥模式就是没有公共属性,并且其余方法也不引用this对象,稳妥模式最适合在安全的环境中使用。若是程序对于安全性要求很高,那么很是适合这种模式。
也不能使用new关键字。
//稳妥构造函数式 durable object (稳妥对象) //1,没有公共的属性 //2,不能使用this对象 function Person ( name,age ) { //建立一个要返回的对象。 利用工厂模式思惟。 var obj = new Object(); //能够定义一下是有的变量和函数 private var name = name || 'zf'; // var sex = '女'; // var sayName = function () { // } //添加一个对外的方法 obj.sayName = function () { console.log(name); } return obj; } var p1 = Person('xixi',20); p1.sayName();
许多OO语言横纵都支持两种继承方式:接口继承 和实现继承。
接口继承:只继承方法签名
实现继承:继承实际的方法。
因为函数中没有签名,在ECMAScript中没法实现接口继承,ECAMScript 只支持实现继承,而其实现继承主要原型链来实现。
原型链
基本思想:让一个引用类型继承另外一个引用类型的属性和方法。
让原型对象等于另外一个类型的实例,此时的原型对象将包含一个指向另外一个原型的指针,相应的,另外一个原型中也包含指向另外一个构造函数的指针。
实现的本质是重写原型对象,代之以一个新类型的实例
function SuperType () { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; } function SubType () { this.subproperty = false; } // 继承了SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.subproperty; } var instance = new SubType(); console.log( instance.getSuperValue() ); // true
调用 instance.getSuperValue()
会经历三个步骤:
1:搜索实例
2:搜索SubType.prototype
3:搜索SuperTpe.prototype
别忘记默认的原型
全部函数的原型都是Object的实例,所以默认原型都会包含一个内部指针,指向Object.prototype肯定原型和实例的关系
能够经过两种方式来肯定原型和实例之间的关系。第一种方式是使用 instanceof 操做符,只要用
这个操做符来测试实例与原型链中出现过的构造函数,结果就会返回 true 。
方式1:使用instanceof操做符。 测试实例与原型链中出现的构造函数,就会返回true。
alert(instance instanceof Object); //true alert(instance instanceof SuperType); //true alert(instance instanceof SubType); //true
方式2:使用isPrototypeOf(); 是原型链中出现过的原型,均可以说是该原型链所派生的实例的原型。就会返回true。
alert(Object.prototype.isPrototypeOf(instance)); //true alert(SuperType.prototype.isPrototypeOf(instance)); //true alert(SubType.prototype.isPrototypeOf(instance)); //true
谨慎地定义方法
子类型有时候须要重写超类型中的某个方法,或者须要添加超类型中不存在的某个方法。
给原型添加方法的代码必定要放在替换原型的语句以后。
即在经过原型链实现继承时,不能使用对象字面量建立原型方法。由于这
样作就会重写原型链,
即在经过原型链实现继承时,不能使用对象字面量建立原型方法。这样会重写原型链。致使替换原型无效。
原型链的问题
原型链能够实现继承,可是存在一些问题:包含引用类型的原型。
经过原型来实现继承时,原型实际上会变成另外一个类型的实例。因而,原先的实例属性也就变成了如今的原型属性了。
在构造函数中,而不是原型对象中定义属性的缘由:包含引用类型的原型属性会被实例所共享。
第二个问题:在建立子类型的实例时,不能向超类型的构造函数中传递参数。
缘由:没有办法在不影响全部对象的实例状况下,给超类型的构造函数传递参数。
实践中不多单独使用原型链。
借用构造函数
在子类型构造函数的内部调用超类型构造函数
函数只是在特定环境中执行代码的对象,所以经过使用apply()和call()方法能够在新建立的对象上执行构造函数
传递参数
在类型构造函数中向超类型构造函数传递参数
function SuperType ( name ) { this.name = name; } function SubType ( name ) { // 继承了SuperType,同时传递参数 SuperType.call(this,name) this.age = 21; } var instance = new SubType('cyan'); console.log( instance.name ); // cyan console.log( instance.age ); // 21
借用构造函数的问题
没法避免构造函数模式存在的问题--方法都定义构造函数中定义。
超类型的原型中定义的方法,对子类型而言也是不可见的。
借用构造函数不多单独使用
组合继承
将原型链和借用构造函数的技术组合到一块,发挥两者之长的一种继承模式。
思路:使用原型链的实现对象原型属性和方法的继承,经过借用构造函数实现的对象属性的继承。
结果:即经过在原型上定义方法实现了函数复用,有可以保证每一个实例都有它本身的属性。
instanceof 和isPrototypeOf(); 可以用于识别基于组合建立的对象。 (JavaScript中最经常使用的继承模式)
缺点:会执行构造函数二次。
原型式继承
借助原型能够基于已有的对象建立新对象,同时还不比所以建立自定义类型。
function object(o){ function F(){} F.prototype = o; return new F(); }
在 object(); 函数内部,先建立了一个临时性的构造函数,而后将传入的对象做为这个构造函数的原型,最后返回这个临时类型的一个新实例。本质上:boject()对传入其中的对象执行了一次浅复制
//2件事: 继承了1次父类的模板,继承了一次父类的原型对象 function Person ( name,age ) { this.name = name; this.age = age; } Person.prototype = { constructor: Person, sayHello: function () { console.log('hello world!'); } } function Boy ( name,age,sex ) { //call 绑定父类的模板函数 实现 借用构造函数继承 只复制了父类的模板 // Person.call(this,name,age); Boy.superClass.constructor.call(this,name,age); this.sex = sex; } //原型继承的方式: 即继承了父类的模板,又继承了父类的原型对象。 // Boy.prototype = new Person(); //只继承 父类的原型对象 extend(Boy,Person); // 目的 只继承 父类的原型对象 , 须要那两个类产生关联关系. //给子类加了一个原型对象的方法。 Boy.prototype.sayHello = function () { console.log('hi,js'); } var b = new Boy('zf',20,'男'); console.log( b.name ); console.log( b.sex ); b.sayHello(); Boy.superClass.sayHello.call(b); //extend方法 //sub子类, sup 父类 function extend ( sub,sup ) { //目的, 实现只继承 父类的原型对象。 从原型对象入手 //1,建立一个空函数, 目的:空函数进行中转 var F = new Function(); // 用一个空函数进行中转。 // 把父类的模板屏蔽掉, 父类的原型取到。 F.prototype = sup.prototype; // 2实现空函数的原型对象 和 超类的原型对象转换 sub.prototype = new F(); // 3原型继承 //作善后处理。 还原构造器 , sub.prototype.constructor = sub; //4 ,还原子类的构造器 //保存一下父类的原型对象 // 由于 ①方便解耦, 减低耦合性 ② 能够方便得到父类的原型对象 sub.superClass = sup.prototype; //5 ,保存父类的原型对象。 //自定义一个子类的静态属性 , 接受父类的原型对象。 //判断父类的原型对象的构造器, (防止简单原型中给更改成 Object) if ( sup.prototype.constructor == Object.prototype.constructor ) { sup.prototype.constructor = sup; //还原父类原型对象的构造器 } }
Object.create()
参数1:用做新对象原型的对象。
参数2(可选)一个为新对象额外属性的对象. 配置参数(每一个属性都是经过高本身的描述符定义)
传入一个参数的状况下:Object.create(); 与 object() 方法的行为相同.
注意:定义任何属性都会覆盖原型对象上同名属性。
包含引用类型值的属性始终都会共享相应的值
var person = { name: 'cyan', firends: ['tan', 'kihaki', 'van'] } var anotherPerson = Object.create(person, { name: { value: 'red' } }); console.log( anotherPerson.name ); // red
寄生式继承
建立一个仅用于封装过程的函数,该函数在内部以某种方式来加强对象,最后返回对象。
function createAnother ( original ) { var cloen = Object.create(original); // 调用函数建立一个新对象 clone.sayHi = function () { // 以某种方式来加强这个对象 console.log('hi'); } return clone; // 返回这个对象 }
寄生组合式继承
解决,构造函数调用二次的问题。
经过借用构造函数来继承属性,经过原型链的混成形式来继承方法。
基本思路:没必要为了指定子类型的原型而调用超类型构造函数。须要的是超类型的原型的一个副本。
本质:使用寄生式继承来继承超类型的原型,而后再将指定给子类型的原型。
没有使用new关键字
function inheritProtoype ( subType, superType ) { var prototype = Object.create(superType.prototype); // 建立对象 prototype.constructor = subType; // 加强对象 subType.prototype = prototype; // 指定对象 } // 1: 建立超类型的原型的一个副本。 // 2:为建立的副本添加添加constructor属性,从而弥补由于重写原型而市区去的constructor属性 // 3: 将新建立的对象,赋值给子类型的原型。
经过name 能够访问函数名(非标准属性)
关于函数声明,重要特征就是函数声明提高:代码执行以前会先读取函数声明。
匿名函数(也叫拉姆达函数): function 关键字后面没有标识符
匿名函数的name属性时空字符串
递归函数: 一个函数经过名字调用自身的状况
function factorial (num) { if ( num <= 1 ) { // 递归出口 return 1; } else { return num * arguments.callee(num-1); // 递归点 } }
命名函数表达式:
var factorial = (function f ( num ) { if ( num <= 1 ) { return 1; } else { return num * f(num-1); } });
建立了一个名为f() 的命名函数表达式,而后将它赋值给变量factorial。即便把函数赋值给另外一个变量,函数的名字f仍然是有效的。
闭包:指有权访问另外一个函数中做用域中的变量的函数。
表现:在一个函数内部建立另外一个函数
当某个函数被调用的时候,会建立一个执行环境(execution context)及相应的做用域链。而后使用arguments和其它命名参数的值来初始化函数的活动对象(activation object)。
在做用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位直至做用域链的终点的全局执行环境。
后台的每一个执行环境都有一个表示变量的对象---变量对象
做用域链中至少包含二个变量对象:本地活动对象和全局变量对象。
做用域链本质:一个指向变量对象的指针列表,引用但不实际包含变量对象。
不管何时在函数中访问一个变量时,就会从做用域链中搜索具备相应名字的变量。
通常来说,当函数执行完毕后,局部活动对象就会被销毁,内容中近保存全局做用域(全局执行环境的比变量对象)。
可是,闭包能够延长变量的生存周期
function createComparisonFunction( propertyName ) { return function ( object1, object2 ) { var val1 = object1[propertyName]; var val2 = object1[propertyName]; if ( val1 < val2 ) { return -1; } else if ( val1 > val2 ) { return 1; } else { return 0; } } } var compare = createComparisonFunction('name'); var reslut = compare({name: 'cyan'}, {name: 'tan'}); // 告知垃圾回收机制将其清除, // 随着匿名函数的做用域链被销毁,其它做用域(除了全局做用域)也均可以安全的销毁。 compare = null; // 接触对匿名函数的引用(以便释放内存)
createComparisonFunction()函数执行完毕后,其活动对象也不会被销毁,由于匿名函数的做用域链仍然在引用这个活动对象。
当createComparisonFunction()函数执行返回后,其执行环境的做用域链会被销毁,但它的活动对象仍会留在内存中,直至匿名函数被销毁。createComparisonFunction()的活动对象才会被销毁。
闭包的问题:
因为闭包会携带包含它的函数的做用域,所以会比其余函数占用过多的内存。过分使用闭包可能会致使内存占用过多,V8引擎优化后,JavaScript引擎会尝试回收被闭包占用的内存。
闭包与变量
闭包只能取得包含函数中任何变量的最后一值。(循环嵌套函数的i问题)
闭包所保存的是整个变量对象,而不是某个特殊的变量。
function createFunctions () { var reslut = []; for ( var i=0; i<10; i++ ) { reslut[i] = function (num) { return function () { return num; } }(i); } return reslut; }
定义一个匿名函数,并将当即执行该匿名函数的结果赋给数组。因为函数参数是按值传递的,因此就会将变量i的当前值复制给参数num。而在这个匿名函数内部,有建立并返回了一个访问num的闭包。这样,reslut数组中的每一个函数都有本身num变量的一个副本,所以能够返回各自不一样的数值。
关于this对象
this对象是运行时就函数执行环境绑定的:在全局函数中,this等于window,而当函数被做为某个对象的方法调用时,this等于那个对象。
匿名函数的执行环境具备全局性,所以其this对象一般指向window。但有时候因为编写闭包的方式不一样,这一点不明显。(在经过call()或apply()来改变函数执行环境的状况下,this就会指向其它对象。)
var name = 'window'; var object = { name: 'object', getNameFunc: function () { return function () { return this.name; } } } console.log( object.getNameFunc()() );
先建立了一个全局变量name,有建立一个包含name属性的对象。这个对象包含一个方法--getNameFunc(); 它返回一个匿名函数,而匿名函数又返回this.name。因为getNameFunc();返回一个函数,所以调用object.getNameFunc()(); 就当即调用它返回的函数,结果返回一个字符串。 结果的name变量的值是全局的。为何匿名函数没有取得其包含做用域(或外部做用域)的this对象呢?
每一个函数在被调用时都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,所以永远不可能直接访问外部函数中的这个两个变量。
能够经过保存this引用来访问
var name = 'window'; var object = { name: 'object', getNameFunc: function () { var self = this; return function () { return self.name; } } } console.log( object.getNameFunc()() );
this 和arguments存在一样的问题,若是想访问做用域中的arguments对象,必须将对该对象的引用保存到另外一个闭包可以访问的变量中。
内存泄漏
若是闭包的做用域链保存着一个HTML元素,就意味着该元素将没法被销毁
闭包会引用包含函数的整个活动对象。
包含函数的活动中也仍然会保存一个引用。所以,有必要把element变量设置null。这样可以接触对DOM对戏那个的引用。顺利地减小其引用数据,确保正常回收其占用的内存。
模仿块级做用域
JavaScript中没有块级做用域的概念
function outputNumbers ( count ) { for ( var i=0; i<count; i++ ) { console.log(i); } console.log(i); // 计数 }
变量i是定义在outputNumbers()的活动对象中,所以从它有定义开始,就能够在函数内部随处访问它。
匿名函数用来模仿块级做用域并避免这个问题。
块级做用域:称为私有做用域
(function () {})();
将函数声明包含在一堆圆括号中,表示它其实是一个函数表达式,而紧随其后的另外一对圆括号会当即调用这个函数。
JavaScript将function 关键字做一个函数声明的开始,而函数后声明不能跟圆括号。而后函数表达式的后面能够跟圆括号。
将函数声明转换成表达式使用()
需求:只要临时须要一些变量,就可使用私有化做用域
function outputNumbers ( count ) { (function () { for ( var i=0; i<count; i++ ) { console.log(i); } })(); console.log(i); // 报错 } // 变量i 只能在循环中使用,使用后即被销毁,而在私有化做用域中可以访问变量count,由于这个匿名函数是一个闭包,它可以访问包含做用域中的全部变量。
在匿名函数中的定义的任何变量,都会在执行结束时被销毁。
结果:减小闭包占用的内存问题,由于没有指向匿名函数的引用。只要函数执行完毕,就能够当即销毁其做用域链了。
特权方法:有权访问私有变量和私有方法的公有方法。
做用:封装性,隐藏那些不该该被被直接修改的属性,方法。
缺点:必须使用构造函数模式来达到这个目的。
构造函数自己是有缺点:对每一个实例都是建立一样一组新方法。
构造函数中定义特权方法:
function MyObject () { // 私有变量和私有函数 var privateVariable = 10; function prvateFunction () { return false; } // 特权方法 this.publicMethod = function () { privateVariable++; return prvateFunction(); } }
静态私有变量
目的:建立静态变量会由于使用原型而增进代码的复用,但没有实例对象都没有本身的私有变量。
使用闭包和私有变量缺点:多查找做用域中的一个层次,就会在必定程度上影响查找速度。
模块模式
目的:为单例建立私有变量和特权方法。
本质:对象字面量定义的是单例的公共接口
单例:只有一个实例的对象。
惯例:JavaScript是以对象字面量的方式来建立单例对象。
做用:对单例进行某些初始化,同时又须要维护其私有化变量。
//经过 一个私有变量来控制是否 实例化对象, 初始化一个 init。 var Ext = {}; Ext.Base = (function () { //私有变量 控制返回的单体对象 var uniqInstance; //须要一个构造器 init 初始化单体对象的方法 function Init () { //私有成员 var a1 = 10; var a2 = true; var fun1 = function () { console.log( a1 ); } return { attr1: a1, attr2: a2, fun1: fun1 } } return { getInstance: function () { if ( !uniqInstance ) { //不存在 ,建立单体实例 uniqInstance = new Init(); } return uniqInstance; } } })() var init = Ext.Base.getInstance(); init.fun1(); //10
使用需求:建立一个对象并以某些数据对齐初始化,同时还要公开一些可以访问这些私有数据方法。
每一个单例都是Object的实例,由于经过一个对象字面量表示单例一般都是 全局对象存在,不会将它传递给一个函数。
加强的模块模式
在返回对象以前假如对其加强的代码。
var application = function(){ //私有变量和函数 var components = new Array(); //初始化 components.push(new BaseComponent()); //建立 application 的一个局部副本 var app = new BaseComponent(); //公共接口 app.getComponentCount = function(){ return components.length; } app.registerComponent = function(component){ if (typeof component == "object"){ components.push(component); } } //返回这个副本 return app; }();
BOM的核心对象是window,表示浏览器的一个实例。
在浏览器中,window对象有双重角色:
1:JavaScript访问浏览器窗口的一个接口
2: ECMAScript规定的Global对象。
在网页中定义的任何一个对象,变量,函数,都已window做为其Global对象。所以有权访问parseInt()等方法。
窗口与框架
使用框架时,每一个框架都有本身的window对象以及全部原生构造函数及其它函数的副本。每一个框架都保存在frames集合中,能够经过位置或经过名称来访问。
top对象始终指向最外围的框架,也就是整个浏览器窗口。
parent 对象表示包含当前框架的框架,而 self 对象则回指 window 。
全局做用域
定义全局变量与在window对象上直接定义属性差异:
全局变量不能经过delete操做删除,直接定义window对象上的定义属性能够删除。
var age = 22; window.color = "red"; //在 IE < 9 时抛出错误,在其余全部浏览器中都返回 false delete window.age; //在 IE < 9 时抛出错误,在其余全部浏览器中都返回 true delete window.color; // returns true console.log(window.age); //22 29 console.log(window.color); // undefined
var 语句添加的window属性[Configurable]的特性,这个特性的值被设置为false。因此定义的不可delete操做符删除。
尝试访问未声明的变量会抛出错误,可是经过window对象,能够知道某个可能未声明的变量是否存在。
var newValue = oldValue; // 报错,未定义 var newValue = window.oldValue; // 属性查询,不会抛出错误
提供了与当前窗口中加载的文档有关的信息,提供一些导航功能。
location对象很特别,便是window对象的属性,也是document对象的属性,window.location 和 document.location引用的是同一个对象。
做用:保存着当前文档的信息,还表现将URL解析为独立的片断。
属性 | 例子 | 说明 |
---|---|---|
hash | "#contents" | 返回URL中的hash(#号后跟零或多个字符),若是URL中不包含散列,则返回空字符串 |
host | "www.aa.com:80" | 返回服务器名称和端口号(若是有) |
hostname | "www.aa.com" | 返回不带端口号的服务器名称 |
href | "http:/www.aa.com" | 返回当前加载页面的完整URL。而location对象的toString()方法也返回这个值 |
pathname | "/WileyCDA/" | 返回URL中的目录和(或)文件名 |
port | "8080" | 返回URL中指定的端口号。若是URL中不包含端口号,则这个属性返回空字符串 |
protocol | "http:" | 返回页面使用的协议。一般是http:或https: |
search | "?q=javascript" | 返回URL的查询字符串。这个字符串以问号开头 |
位置操做
location对象改变浏览器位置
location.assign('http://segmentfault.com'); // 打开新的URL并在浏览器的历史记录中生成一条记录。
若是将location.href 或window.location设置为一个URL,会以该值调用assign()方法。
window.location = "http://www.segmentfault.com"; location.href = "http://www.segmentfault.com";
最经常使用的方式:location.href = "http://www.segmentfault.com";
禁止用户使用‘后退’按钮 使用location.replace();
参数:须要导航的URL
location.relaod(); 做用:从新加载当前显示的页面
location.reload(); //从新加载(有可能从浏览器缓存中加载) location.reload(true); //从新加载(从服务器从新加载)
location.reload();调用以后的代码可能会也可能不会执行,取决于网络延迟或系统资源等因素。若是使用location.relaod();最好放在代码最后一行。
做用:识别客户端浏览器
navigator.userAgent // 浏览器的用户代理字符串
检测插件
非IE下使用:navigator.plugins数组
function hasPlugin(name){ name = name.toLowerCase(); for (var i=0; i < navigator.plugins.length; i++){ if (navigator. plugins [i].name.toLowerCase().indexOf(name) > -1){ return true; } } return false;
注册处理程序
registerContentHandler() 和 registerProtocolHandler()
做用:让一个站点指明它能够处理特定类型的信息。(使用范围:RSS 阅读器和在线电子邮件程序)
registerContentHandler();
参数1:要处理的MIME类型
参数2:能够处理该MIME类型的页面URL
参数3:应用程序的名称
// 站点注册为处理 RSS 源的处理程序 navigator.registerContentHandler("application/rss+xml","http://www.somereader.com?feed=%s", "Some Reader"); // 参数1:RSS源 的MIME类型参数 2:应该接收 RSS源 URL的 URL,其中的%s 表示RSS 源 URL,由浏览器自动插入。 // 做用:当下一次请求 RSS 源时,浏览器就会打开指定的 URL,而相应的Web 应用程序将以适当方式来处理该请求。
代表客户端能力:浏览器窗口外部的显示器信息,如:像素宽度和高度。
window.screen.height // 屏幕的像素高度
window.screen.windth // 屏幕的像素宽度
window.resizeTo(); // 调整浏览器窗口大小
history 对象:保存用户上网的历史记录。充窗口被打开的那一刻算起。
history是window对象的属性,所以每一个浏览器串窗口,每一个标签页乃至每一个框架,都有本身的history对象与特定的window对象关联。
history.go(); 在用户的历史记录中任意跳转。
//后退一页 history.go(-1); //前进一页 history.go(1); //前进两页 history.go(2); // go()参数是字符串 // 可能后退,也可能前进,具体要看哪一个位置最近 //跳转到最近的 wrox.com 页面 history.go("segmentfault.com");
history.length; 保存着历史记录的数量
包括全部历史记录,即全部向后向前的记录。对于加载到窗口,标签页或框架中的第一个页面而言。
安全的类型检测
JavaScript 中内置的类型检测机制并不是彻底可靠
typeof操做符,因为它有一些没法预知的行为,致使检测数据类型时获得不靠谱的结果。(Safari直至第四版,对正则表达式 typeof 检测 会返回 'function')
instanceof操做符,存在多个全局做用域(像一个页面中包含多个frame)的状况下。
var isArray = valeu instaceof Array; // 返回true 条件:value 必须是数组, 必须与Array构造函数在同个全局做用域中。(Array 是 window的属性)。
在任何值上调用Object原生的toString();方法。
返回 [object NativeConstructorName]格式的字符串。每一个类在内部都有一个[[Class]]属性,这个属性中指定了这个字符串中的构造函数名.
应用:
检测原生JSON对象。
var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON) === '[object JSON]';
做用域安全的构造函数
做用:自定义对象和构造函数的定义和用法。
构造函数就是一个使用new操做符调用的函数。当使用new调用时,构造函数内用到的this对象会指向新建立的对象实例。
问题:当没有使用new操做符来调用该构造函数的状况下。this对象是在运行时绑定的,因此直接调用 类名 ,this会映射到全局对象window上,致使错误对象属性的之外增长。
function Person ( name, age ) { this.name = name; this.age = age; } var p1 = Person('cyan', 22); console.log(window.name); console.log(window.age); // Person实例的属性被加到window对象上,由于构造函数时做为普通函数调用,忽略了new操做符。由this对象的晚绑定形成的,在这里this被解析成了window对象。
// 在类中添加判断: function Person ( name, age ) { if ( this instanceof Person ) { this.name = name; this.age = age; } else { return new Person(name, age); } } var p1 = Person('cyan', 22); console.log(window.name); console.log(window.age); // 添加一个检查,并确保this对象是Person实例。 // 要么使用new操做符,要么使用现有的Person实例环境中调用构造函数。
函数绑定
函数绑定要建立一个函数,能够在待定的this环境中指定参数调用另外一个函数。
使用范围:回调函数与事件处理程序一块儿使用,(在将函数做为变量传递的同时保留代码执行环境)
// bind(); 函数 function bind ( fn, context ) { return function () { return fn.apply(context, arguments); } }
将某个函数以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定的函数的效果。
主要用于事件处理程序以及setTimeout() 和 setInterval();
被绑定函数与普通函数相比有更多开销,须要更多内存支持,同时也由于多重函数调用调用比较慢。最好只在必要时使用。
函数柯里化
函数柯里化( function currying ): 把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并返回接受余下的参数且返回结果的新函数
做用:建立已经设置好的一个或多个参数的函数。
函数柯里化的基本方法:使用一个闭包返回一个函数。
函数柯里化和函数绑定区别:当函数被调用时,返回的函数还须要设置一些传入的参数。
函数柯里化的动态建立:调用另外一个函数并为它传入要柯里化的函数和必要的参数。
用处:
做为函数绑定的一部分包含在其中,构造出更为复杂函数
function bind ( fn, context ) { let args = Array.prototype.slice.call(arguments, 2); return { let innerArgs = Array.prototype.slice.call(arguments); let fianlArgs = args.concat(innerArgs); return fn.apply(context, fianlArgs); } }
缺点:每一个函数都会带来额外的开销.不可以滥用。