javascript高级程序设计——笔记
基本概念
- 基本数据类型包括Undefined/Null/Boolean/Number和String
- 无须指定函数的返回值,实际上,未指定返回值的函数返回的是一个特殊的undefined值
变量、作用域和内存问题
- 基本类型值在内存中占据固定大小的空间,因此保存在栈内存中
- 引用类型的值是对象,保存在堆内存中
- 确定一个值是哪种基本类型用typeof,确定一个值是哪种引用用instanceof
- 所有变量都存在于一个执行环境当中,这个执行环境决定了变量的生命周期,以及如一部分代码可以访问其中的变量。执行环境有全局执行环境和函数执行环境,每次进入一个新执行环境,都会创建一个用于搜索变量和函数的作用链。
- Javascript是一门具有自动垃圾收集机制的编程语言,离开作用域的值被自动标记为可以回收,“标记清除”是目前主流的垃圾收集算法,另一种是“引用记数”,这种算法的思想是跟踪记录所有值被引用的次数。在IE中访问非原生Javascript对象(DOM元素)时,这种算法会因为循环引用导致引用计数永远无法清零
Array类型
- concat()先创建当前数组一个副本,然后接收的参数添加到副本末尾,最后返回
- slice()基于当前数组中的一个或多个项创建一个新数组
- splice()数组的增删
- indexOf()/lastIndexOf()查找项和查找起点位置,比较用的===。支持IE9+
- every()=and
- filter()返回函数返回为true的项数组
- forEach()运行指定函数,无返回
- map()返回每次函数调用的结果组成的数组
- some()=||
- reduce()/reduceRight(),function(prev,cur,index,array),IE9+
Date类型
var now = new Date()//Date.parse()和Date.UTC()
var someDate = new Date(Date.parse("May 25,2004"));
var y2k = new Date(Date.UTC(2000,0)//GMT时间2000年1月1日午夜零时
RegExp类型
- RegExp类型中需要转义的元字符包括([{\^$|)?*+=.]}
var pattern1 = /[bc]at/i;
var pattern2 = new RegExp("[bc]at","i");
RegExp构造函数的两个参数都是字符串,在某些情况下要对字符进行双重转义
字面量模式 | 等价的字符串 |
---|---|
/\ [bc\]at/ | “\\[bc\\]at” |
/\w\\hello\\123/ | “\\w\\\\\hello\\\\123” |
- RegExp实例属性:global是否设置了g标志,ignoreCase是否设置了i标志,lastIndex表示开始搜索下一个匹配项的字符位置,multiline是否设置m 标志,source正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。正则表达式字面量始终会共享同一个RegExp实例,所以在循环时需要注意,会从上一次搜索位置开始匹配。
- exec()专门为捕获组而设计,没有匹配返回null,返回数组包含两个额外属性:index和input。其中index表示匹配项在字符串中的位置,input表示应用正则表达式的字符串。数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果没有则数组只有一项)。
- test()匹配返回true否则false
Function类型
-函数内部对象arguments和this,arguments除了保存函数参数,还有一个属性callee指向这个arguments对象的函数
function factorial(num){
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num-1);
}
}//通过arguments.callee消除函数名的紧密耦合
-this引用的是函数据以执行的环境对象——或者也可以说是this值。
-ECMAScript5规范化了另一个函数对象属性:caller。除了Opera早期版本不支持。这个属性中保存调用当前函数的函数的引用,如果在全局作用域中调用,则caller的值为null。
-length表示希望接收的命名参数的个数。
-prototype是保存引用类型所有实例方法的真正所在。诸如toString()和valueOf()等方法实际上都保存在prototype名下,只不过是通过各自对象的实例访问罢了。该属性不可枚举。
-每个函数都包含两个非继承方法:apply()和call()。作用是在特定的作用域调用函数,实际上等于设置函数体内this对象的值。区别在于传入第二个参数是arguments对象还是具体参数。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor();//red
sayColor.call(this);
sayColor.call(window);
sayColor.call(o);//blue
var objectSayColor = sayColor.bind(o);//即使在全局作用域中调用该函数,也会看"blue",IE9+支持
基本包装类型
Boolean、Number、String
var s1 = "some text";
var s2 = s1.substring(2);
var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;//自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁。
var value = "25";
var number = Number(value);//转型函数
var object = new Number(value);//构造函数
var num = 10.005;
alert(num.toFixed(2)); //"10.01"
alert(num.toExponential(1));//"1.0e+1"
var num = 99;
alert(num.toPrecision(1));//"1e+2"
- 字符串操作方法
concat()/slice()/substr()/substring()都不会修改字符串本身的值,只是返回一个基本类型的字符串,对原始字符串没有任何影响。
indexOf()/lastIndexOf(),没有找到返回-1
trim()返回字符串副本,IE9+支持
match(正则/RegExp)同exec()
search(正则/RegExp)无匹配返回-1
replace(RegExp/字符串,字符串/函数)
split(RegExp/字符串,length)
charCodeAt()/formCharCode()
单体内置对象
- Global对象
encodeURI()/encodeURIComponent()对URI(Uniform Resource Identifiers,能用资源标识符)进行编码,以便发送给浏览器。区别在于前者不会对本身属于URI的特殊字符进行编码,例如冒号、正斜杠、问号和井号;后者会对所有非标准字符进行编码。替代只能编码ASCII字符的escape()
- eval()参数当然实际的ECMAScript语句来解析,然后把执行结果插入到原来位置。在eval()中创建的任何变量和函数都不会被提升。在严格模式下无法访问 eval中创建的变量和函数。
- undefined/NaN/Infinity以及所有原生引用类型的构造函数都是Global的属性
- Math.min()/max()/ceil()/floor()/round()/random()
2-10整数间的随机数,包括2跟10
var num = Math.floor(Math.random()*9+2);
function selectFrom(lowerValue, upperValue) {
var choices = upperValue - lowerValue + 1;
return Math.floor(Math.random()*choices+lowerValue);
}
面向对象的程序设计
对象是散列表:无非就是一组名值对,其中值可以是数据或函数。
- 属性类型
- 数据属性
[[Configurable]]能否delete属性从而重新定义属性,能否修改属性描述,默认true
[[Enumerable]]能否通过for-in循环,默认true
[[Writable]]能否修改属性值,默认true
[[Value]]包含这个属性的数据值,默认undefined
- 访问属性
同上前2
[[Get]]读取属性时调用的函数,默认undefined
[[Set]]写入属性时调用的函数,默认undefined
Object.defineProperty(),configurable设为false后再调用该方法就会抛出错误了,该方法调用时,如果不指定默认都是false。访问属性不能直接定义,必须使用Object.defineProperty()来定义。
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; } }); var descriptor = Object.getOwnPropertyDescriptor(book,"year"); alert(descriptor.value);//undefined alert(descriptor.enumerable);//false alert(typeof descriptor.get);//"function" book.year = 2005; alert(book.edition);//2,使用访问器属性一般是设置一个属性会导致其他属性发生变化 var descriptor = Object.getOwnPropertyDescriptor(book,"_year"); alert(descriptor.value);//2005 alert(descriptor.configurable);//false alert(typeof descriptor.get);//"undefined"
不一定要同时指定getter和setter。在非严格模式下会返回undefined或被忽略,在严格模式下会抛出错误。ECMAScript 5又定义了Object.definedProperties()。
IE9+(IE8部分支持)、Firefox 4+、Safari 5+、Opera 12+、Chrome
- 数据属性
创建对象
- 工厂模式
没法解决对象识别的问题,即对象类型问题。
- 构造函数模式
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); } } var person = new Person("Nicholas",28,"software engineer");
- 创建一个新对象;
- 将构造函数的作用域赋给新对象;
- 执行构造函数中的代码;
- 返回新对象。
构造函数与其他函数的唯一区别在于调用它们的方式不同。任何函数,只要通过new来调用。
//当作构造函数使用
var person = new Person("Nicholas", 28,"software engineer");
person.sayName();
//作为普通函数调用
Person("Greg",23,"Doctor");
window.sayName();//"Greg"
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o,"Kristen",21,"Nurse");
o.sayName();//"Kristen"
但此模式还是有缺点就是不同实例上的同名函数是不相等的,然而创建两个完成同样任务的Function实例是没有必要的;况且有this对象在,根本不用在执行代码前就把函数绑定到特定对象上面。因此可以像下面一样解决此问题。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
alert(this.name);
}//此方法也存在问题,全局函数只被某个对象调用,而且会产生很多这样的全局函数
- 原型模式
创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,该对象的用途是包含可以由特定类型的所有实例共享的属性和方法,prototype就是通过调用构造函数而创建的那个对象实例的原型对象。
javascript
alert(Person.prototype.isPrototypeOf(person1));//true,这里用的原型对象的isPrototypeOf()方法
alert(Object.getPrototypeOf(person1 == Person.prototype);//ECMAScript5增加了Object.getPrototypeOf()返回对象的原型,IE9+/Firefox 3.5+/Safari 5+/Opera 12+和chrome
可以通过对象实例访问保存在原型中的值,但不能重写原型中的值,可以屏蔽不可以重写。想要取回原型中的值用delete person1.name;
alert(person1.hasOwnProperty("name"));//false
alert("name" in person1);//true,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。for-in循环是对象访问的、可枚举的属性。
function hasPrototypeProperty(object, name) {
return !object.hasOwnProperty(name) && (name in object);
}//确定属性是否为原型属性
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.keys()只包含可枚举的实例属性
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys);//"constructor,name,age,job,sayName" Object.getOwnPropertyNames()可以得到所有实例属性,无论它是否可枚举
Object.keys和Object.getOwnPropertyNames都可用来替代fon-in循环,IE9+、Firefox 4+、Safari 5+、Opera 12+和Chrome支持。
function Person(){
}
var friend = new Person();
Person.prototype = {
name:"Nicholas",
age:29,
job:"Software Engineer",
sayName:function(){
alert(this.name);
}
};//Person.prototype设置为一个以对象字面量形式创建的新对象。最新结果相同,但constructor属性不再指向Person了。每创建一个函数,就会同时创建它的prototype对象,这个对象会自动获得constructor属性。这里重写了默认的prototype对象,该新的对象继承了原型对象(Object.prototype)的constructor属性(指向Object构造函数),因此这时Person.prototype.constructor属性不再指向Person函数而是指向Object构造函数。
Person.prototype = {
constructor:Person,
name:"Nicholas",
age:29,
job:"Software Engineer",
sayName:function(){
alert(this.name);
}
};//该种方法会导致重设的constructor属性的[[Enumerable]]特性被设置为true,默认是false。
Object.defineProperty(Person.prototype,"constructor",{
enumberable:false,
value:Person
});
friend.sayName();//error,因为friend指向的原型中不包含以该名字命名的属性。重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;它们引用的仍然是最初的原型。
尽管可以随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来,但如果重写整个原型对象,情况就不一样了。调用构造函数时会为实例添加一个指向最初原型的[[Prototype]]指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。
原型对象的缺点,省略了为构造函数传递初始化参数这一环节还有就是共享所导致,当包含引用类型值的属性时会有共享问题。
- 组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性,同时又支持向构造函数传递参数。
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);
alert(person2.friends);//"Shelby,Count"
alert(person1.friends === person2.friends);//false
alert(person1.sayName === person2.sayName);//true
- 动态原型模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
if(typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
};
}
}
- 寄生构造函数模式
function SpecialArray() {
var values = new Array();
values.push.apply(values, arguments);
values.toPipedString = function() {
return this.join("|");
};
return values;
}
该种模式下返回的对象与构造函数或者与构造函数的原型属性之间没有关系,既构造函数返回的对象与在构造函数外部创建的对象没什么不同,因此不能依赖instanceof操作符来确定对象类型。
- 稳妥构造函数模式
稳妥对象指没有公共属性,而且其方法也不引用this的对象。
javascript
function Person(name, age, job) {
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function() {
alert(name);
};
//返回对象
return o;
}//除了使用sayName()方法外,没有其他办法访问name值(传入到构造函数中的原始数据)
var friend = Person("Nicholas",29,"Software Enginner");
friend.sayName();
稳妥构造函数模式提供的这种安全性适合在某些安全执行环境。与寄生构造函数模式类似,该模式创建的对象与构造函数之间没什么关系。
继承
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
//继承了Supertype
SubType.prototype = new SuperType();//property实例属性位于SubType.prototype中,SubType.prototype.constructor = SuperType
SubType.prototype.getSubValue = function() {
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());//true
调用instance.getSuperValue()会经历三个搜索步骤:1)搜索实例2)搜索SubType.prototype;3)搜索SuperType.prototype,最后一步才会找到
实现的本质是重写原型对象,代之以一个新类型的实例。原来存在于SuperType的实例中的所有属性和方法,现在也存在于SubType.prototype中了。
原型链的问题:包含引用类型值的原型;在创建子类型的实例时,不能向超类型的构造函数传递参数,所以实践中很少会单独使用原型链。
借用构造函数
在子类型构造函数的内部调用超类型构造函数。解决了原型链的问题。
function SuperType() {
this.colors = ["red", "green"];
}
function SubType() {
//继承了SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);//"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors);//"red,blue,green"
虽然如此还是有问题:无法避免方法都在构造函数上定义,就无法函数复用了。在超类的原型中定义的方法对子类而言是不可见的,所以借用构造模式也很少用。
组合模式
原型链和借用构造函数组合在一块,使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue","green"];
}
SuperType.prototype.sayName = function() {
alert(this.name);
};
function SubType(name, age) {
//继承属性
SuperType.call(this, name);//第二次调用SuperType构造函数
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();//第一次调用Supertype构造函数
SubType.prototype.sayAge = function() {
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);//"red,blue,green,black"
instance1.sayName();//"Nicholas"
instance1.sayAge();//29
var instance2 = new SubType("Greg",27);
alert(instance2.colors);//"red,blue,green"
instance2.sayName();//"Greg"
instance2.sayAge();//27
原型式继承
function object(o) {
function F(){};
F.prototype = o;
return new F();
}
借助原型可以基于已有的对象创建新对象,不必因此创建自定义类型。ECMAScript5通过新增Object.create()方法规范化了原型式继承。接收参数:一个用作新对象原型的对象和(可选)一个为新对象定义额外属性的对象。一个参数时等同于上面的object(),在没有必要创建构造函数时使用。
寄生式继承
与寄生构造函数和工厂模式类似,创建一个仅用于封闭继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
function createAnther(original) {
var clone = object(original);//通过调用函数创建一个新对象
clone.sayHi = function(){
//以某种方式来增强这个对象
alert("hi");
};
return clone;//返回这个对象
}
寄生组合式继承
组合继承的不足是无论什么情况下都会调用两次超类型构造函数(创建子类型原型的时候和子类型构造函数内部)。寄生组合式继承解决此问题,思路:使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。基本模式如下:
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype);//创建超类型原型的一个副本
prototype.constructor = subType;//增强对象,为创建的副本添加constructor属性
subType.prototype = prototype;//指定对象,将创建的副本赋值给子类型的原型
}
将前面组合继承中的语句SubType.prototype = new SuperType();替换成inheritPrototype(SubType,SuperType);效率体现在它只调用了一次SuperType构造函数,并且因此避免了在SubType.prototype上面创建不必要的、多余的属性。与此同时,原型链还能保持不变。
寄生组合式继承是引用类型最理想的继承范式。
总结
- 创建对象
- 工厂模式:使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。
- 构造函数模式:可以创建自定义引用类型,像创建内置对象实例一样使用new操作符。缺点是每个成员都无法得到复用,包括函数。
- 原型模式:使用构造函数的prototype属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法。
js主要通过原型链实现继承,通过将一个类型的实例赋值给另一个构造函数的原型来实现,子类型能够访问超类型的所有属性和方法。问题在于对象实例共享所有继承的属性和方法。解决方法是借用构造函数,即在子类型构造函数的内部调用超类型构造函数。使用最多的是继承模式是组合继承,使用原型链继承共享的属性和方法,通过借用构造函数继承实例属性。
- 原型式继承:执行对给定对象的浅复制,复制得到的副本还可以得到进一步改造。
- 寄生式继承:基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。为了解决组合继承模式由于多次调用超类构造函数而导致低效率问题,可以将此模式与组合继承一起使用。
- 寄生组合式继承:集寄生式继承和组合继承的优点与一身。
目录
用 [TOC]
来生成目录:
- javascript高级程序设计笔记
- 基本概念
- 变量作用域和内存问题
- Array类型
- Date类型
- RegExp类型
- Function类型
- 基本包装类型
- 单体内置对象
- 面向对象的程序设计
- 创建对象
- 继承
- 目录