写在前面:暂时只是粗略的排版,以后还会整理。也可到 http://bulabula.top/高三笔记/笔记4-6.txt 下载TXT版。。
四、变量、作用域和内存问题
4.1 基本类型和引用类型的值
1)、ECMAScript 变量可能包含两种不同数据类型的值:基本类型值和引用类型值。
基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。
2)、基本数据类型:Undefined、Null、Boolean、Number 和String。基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
3)、引用类型的值是保存在内存中的对象。。在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的。
4.1.1 动态的属性
1)、定义基本类型值和引用类型值的方式是类似的:创建一个变量并为该变量赋值。引用类型的值,我可以为其添加属性和方法,也可以改变和删除其属性和方法。但是不能给基本类型的值添加属性。
4.1.2 复制变量值
1)、从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。
当从一个变量向另一个变量复制引用类型的值时,会将存储在变量对象中的值复制一份放到为新变量分配的空间中。这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量。
4.1.3 传递参数
1)、ECMAScript 中所有函数的参数都是按值传递的。基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。
注:JS中的基本类型按值传递,对象类型按共享传递的。调用函数传参时,函数接受对象实参引用的副本(既不是按值传递的对象副本,也不是按引用传递的隐式引用)。 它和按引用传递的不同在于:在共享传递中对函数形参的赋值,不会影响实参的值。
4.1.4 检测类型
1)、typeof 操作符是检测一个变量是否是基本类型的最佳工具。
2)、确定一个值是哪种基本类型可以使用typeof 操作符,而确定一个值是哪种引用类型可以使用instanceof 操作符。
4.2 执行环境作用域
1)、每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。
2)、当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。
3)、如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量(arguments 对象)。
4)、变量查询会先从当前局部环境的变量对象开始一层一层想上查找直到搜索到全局环境的变量对象。
4.3 垃圾收集
1)、局部变量的生命周期:在栈(或堆)内存上分配相应的空间=>使用=>函数执行结束=>释放
2)、JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。“标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然后再回收其内存。
另一种不太常见的垃圾收集策略叫做引用计数(reference counting)。
3)、解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。
五、引用类型
5.1 Object类型
1)、创建Object 实例的方式有两种。第一种是使用new 操作符后跟Object 构造函数。另一种方式是使用对象字面量表示法。
2)、访问对象属性有两种方法:person.name 和 person[name]。
5.2 Array类型
1)、创建Object的方式:var arr = new Objcet(); 和 var arr = [];
2)、数组的length属性不是只读的,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。如:arr[arr.length] = "我是新加的";
3)、判断对象是不是数组:Array.isArray()。
4)、数组继承的toLocaleString()、toString()和valueOf()方法,在默认情况下都会以逗号分隔的字符串的形式返回数组项。
join()可以使用不同的分隔符来构建这个字符串。
5)、栈是一种LIFO(Last-In-First-Out,后进先出)的数据结构。栈中项的插入(叫做推入)和移除(叫做弹出),只发生在一个位置——栈的顶部。
6)、队列数据结构的访问规则是FIFO(First-In-First-Out,先进先出)。
7)、数组方法:
栈方法:
push()方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。
pop()方法则从数组末尾移除最后一项,减少数组的length 值,然后返回移除的项。
队列方法:
shift()方法能够移除数组中的第一个项并返回该项,同时将数组长度减1。
unshift()方法能在数组前端添加任意个项并返回新数组的长度。
重排序方法:
reverse()方法会反转数组项的顺序。
sort()方法按升序排列数组项。sort()方法会先调用每个数组项的toString()方法,然后排序。sort()方法可以接收一个比较函数作为参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回0,如果第一个参数应该位于第二个之后则返回一个正数。
操作方法:
concat()方法可以基于当前数组中的所有项创建一个新数组。具体来说,这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给concat()方法传递参数的情况下,它只是复制当前数组并返回副本。如果传递给concat()方法的是一或多个数组,则该方法会将这些数组中的每一项都添加到结果数组中。如果传递的值不是数组,这些值就会被简单地添加到结果数组的末尾。
slice()方法能够基于当前数组中的一或多个项创建一个新数组。slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下,slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。注意,slice()方法不会影响原始数组。
splice()方法的主要用途是向数组的中部插入项。注:修改原数组的同时返回受影响值的数组。
删除:可以删除任意数量的项,只需指定2 个参数:要删除的第一项的位置和要删除的项数。例如,splice(0,2)会删除数组中的前两项。
插入:可以向指定位置插入任意数量的项,只需提供3 个参数:起始位置、0(要删除的项数)和要插入的项。如果要插入多个项,可以再传入第四、第五,以至任意多个项。例如,splice(2,0,"red","green")会从当前数组的位置2 开始插入字符串"red"和"green"。
替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定3 个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如,splice (2,1,"red","green")会删除当前数组位置2 的项,然后再从位置2 开始插入字符串"red"和"green"。
位置方法:
indexOf()方法从数组的开头(位置0)开始向后查找。
lastIndexOf()方法则从数组的末尾开始向前查找。
迭代方法:
every():对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true。
filter():对数组中的每一项运行给定函数,返回该函数会返回true 的项组成的数组。
forEach():对数组中的每一项运行给定函数。这个方法没有返回值。
map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
some():对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。
以上方法都不会修改数组中的包含的值。
归并方法:
reduce()方法从数组的第一项开始,逐个遍历到最后。
reduceRight()则从数组的最后一项开始,向前遍历到第一项。
注:给reduce()和reduceRight()的函数接收4 个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。
5.3 Date类型
1)、Date类型使用自UTC(Coordinated Universal Time,国际协调时间)1970 年1 月1 日午夜(零时)开始经过的毫秒数来保存日期。在使用这种数据存储格式的条件下,Date 类型保存的日期能够精确到1970 年1月1 日之前或之后的285 616 年。
2)、创建一个日期对象:var date = new Date();
3)、Date.parse("年/月/日")和Date.UTC(年,月,日,时,分,秒)这两个方法会把传入的日期转为毫秒数。
4)、Data.now()方法获得当前时间。支持的浏览器包括IE9+、Firefox 3+、Safari 3+、Opera 10.5 和Chrome。在不支持它的浏览器中,使用+操作符把Data 对象转换成字符串,也可以达到同样的目的。
5)、Date 类型格式化为字符串的方法:
toDateString()——以特定于实现的格式显示星期几、月、日和年;
toTimeString()——以特定于实现的格式显示时、分、秒和时区;
toLocaleDateString()——以特定于地区的格式显示星期几、月、日和年;
toLocaleTimeString()——以特定于实现的格式显示时、分、秒;
toUTCString()——以特定于实现的格式完整的UTC 日期。
6)、日期/时间组件方法
getTime() 返回表示日期的毫秒数,与valueOf()方法返回值相同。
setTime() 以毫秒数设置日期,会改变整个日期。
getFullYear() 取得四位数年份。
getUTCFullYear() 返回UTC日期的四位年份。
setFullYear(年) 设置四位数年份。
setUTCFullYear(年) 设置UTC日期四位数年份。
getMonth() 返回日期中的月份。0表示1月 11表示12月。
getUTCMonth() 返回UTC日期中的月份。0表示1月 11表示12月。
setMonth(月) 设置日期的月份。传入值必须大于0,若大于11则增加年份。
setUTCMonth(月) 设置UTC日期的月份。传入值必须大于0,若大于11则增加年份。
getDate() 返回日期月份中的天数(1-31)。
getUTCDate() 返回UTC日期月份中的天数(1-31)。
setDate(日) 设置日期月份中的天数。如果传入值大于当月应有天数,则增加月份。
setUTCDate(日) 设置UTC日期月份中的天数。如果传入值大于当月应有天数,则增加月份。
getDay() 返回日期中星期的星期几。0表示星期日,6表示星期六。
getUTCDay() 返回UTC日期星期的星期几。0表示星期日,6表示星期六。
getHours() 返回日期中的小时数(0-23)。
getUTCHours() 返回UTC日期中的小时数(0-23)。
setHours(时) 设置日期中的小时数。传入的值超过23则增加月份中的天数。
setUTCHours(时) 设置UTC日期中的小时数。传入的值超过23则增加月份中的天数。
getMinutes() 返回日期中的分钟数(0-59)。
getUTCMinutes() 返回UTC日期中的分钟数(0-59)。
setMinutes(分) 设置日期中的分钟数。传入值大于59则增加小时数。
setUTCMinutes(分) 设置UTC日期中的分钟数。传入值大于59则增加小时数。
getSeconds() 返回日期中的秒数(0-59)。
getUTCSeconds() 返回UTC日期中的秒数(0-59)。
setSeconds(秒) 设置日期中的秒数。传入值大于59则增加分数。
setUTCSeconds(秒) 设置UTC日期中的秒数。传入值大于59则增加分数。
getMilliseconds() 返回日期中的毫秒数。
getUTCMilliseconds() 返回UTC日期中的毫秒数。
setMilliseconds(毫秒) 设置日期中的毫秒数。
setUTCMilliseconds(毫秒) 设置UTC日期中的毫秒数。
getTimezoneOffset() 返回本地时间与UTC时间相差的分数。
5.4 RegExp类型
1)、创建正则表达式:var expression = / pattern / flags ; 模式(pattern)部分可以是任何简单或复杂的正则表达式;flags,用以标明正则表达式的行为。
2)、正则表达式的匹配模式支持下列3 个标志:
g:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
i:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写;
m:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。
3)、RegExp实例属性
global:布尔值,表示是否设置了g 标志。
ignoreCase:布尔值,表示是否设置了i 标志。
lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从0 算起。
multiline:布尔值,表示是否设置了m 标志。
source:正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。
4)、RegExp实例方法
exec()方法:接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回null。
test()方法:接受一个字符串参数。在模式与该参数匹配的情况下返回true;否则,返回false。
5)、RegExp构造函数属性
5.5 Function类型
1)、每个函数都是Function 类型的实例,而且都与其他引用类型一样具有属性和方法。函数名是一个指向函数对象的指针,不会与某个函数绑定。
5.5.2、函数声明和函数表达式
1)、解析器在向执行环境中加载数据时,会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。
5.5.3、作为值的函数:
1)、要访问函数的指针而不执行函数的话,必须去掉函数名后面的那对圆括号。
5.5.4 函数内部属性
1)、在函数内部,有两个特殊的对象:arguments 和this。
2)、caller属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null。
5.5.5 函数属性和方法
1)、每个函数都包含两个属性:length 和prototype。调用length属性会输出参数个数。prototype 属性是不可枚举的,因此使用for-in 无法发现
2)、每个函数都包含两个非继承而来的方法:apply()和call()。它们真正强大的地方是能够扩充函数赖以运行的作用域。
apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array 的实例,也可以是arguments 对象。
call()方法接收两个参数:一个是在其中运行函数的作用域,其余参数都直接传递给函数。
3)、 bind()方法会创建一个函数的实例,其this 值会被绑定到传给bind()函数的值。
5.6 基本包装类型
1)、为了便于操作基本类型值,ECMAScript提供了3 个特殊的引用类型:Boolean、Number 和String。
2)、str.substring(2); 处理顺序:创建String类型的实例 => 在实例上调用 => 销毁实例 。
3)、操作基本类型值的语句一经执行完毕,就会立即销毁新创建的包装对象。
5.6.1 Boolean类型
1)、Boolean 类型是与布尔值对应的引用类型。要创建Boolean 对象,可以像下面这样调用Boolean构造函数并传入true 或false 值。var booleanObject = new Boolean(true);
2)、布尔表达式中的所有对象都会被转换为true,因此falseObject 对象在布尔表达式中代表的是true。
3)、typeof 操作符对基本类型返回"boolean",而对引用类型返回"object"。
5.6.2 Number类型
1)、Number 是与数字值对应的引用类型。要创建Number 对象,可以在调用Number 构造函数时向其中传递相应的数值。下面是一个例子。var numberObject = new Number(10);
2)、toFixed()方法会按照指定的小数位返回数值的字符串表示。
3)、toExponential(),该方法返回以指数表示法(也称e 表示法)表示的数值的字符串形式。
4)、toPrecision()方法可能会返回固定大小(fixed)格式,也可能返回指数(exponential)格式;。这个方法接收一个参数,即表示数值的所有数字的位数(不包括指数部分)。
5)、在使用typeof 操作符测试基本类型数值时,始终会返回"number",而在测试Number 对象时,则会返回"object"。
5.6.3 String类型
1)、String 类型的每个实例都有一个length 属性,表示字符串中包含多个字符。
2)、字符方法:
charAt()和charCodeAt()两个方法都接受一个参数(基于0 的字符位置)。前者返回该位置的字符串,后者返回该位置的字符串编码。
3)、 字符串操作方法
concat()方法用于将一或多个字符串拼接起来,返回拼接得到的新字符串。不改变原字符串值。
slice()、substr()和substring()三个方法都会返回被操作字符串的一个子字符串,而且也都接受一或两个参数。第一个参数指定子字符串的开始位置,第二个参数(在指定的情况下)表示子字符串到哪里结束。具体来说,slice()和substring()的第二个参数指定的是子字符串最后一个字符后面的位置。而substr()的第二个参数指定的则是返回的字符个数。三个方法都不改变源字符串。
4)、字符串位置方法
indexOf()和lastIndexOf()两个方法都是从一个字符串中搜索给定的子字符串,然后返子字符串的位置(如果没有找到该子字符串,则返回-1)。
5)、trim()方法
trim()方法会创建一个字符串的副本,删除前置及后缀的所有空格,然后返回结果。
6)、字符串大小写转换方法
toLowerCase()、toLocaleLowerCase()、toUpperCase()和toLocaleUpperCase()。
7)、字符串的模式匹配方法
match()方法与调用RegExp 的exec()方法相同。只接受一个参数,要么是一个正则表达式,要么是一个RegExp 对象。
search()方法返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回-1。
replace()方法。这个方法接受两个参数:第一个参数可以是一个RegExp 对象或者一个字符串(这个字符串不会被转换成正则表达式),第二个参数可以是一个字符串或者一个函数。
split(),这个方法可以基于指定的分隔符将一个字符串分割成多个子字符串,并将结果放在一个数组中。
8)、fromCharCode()方法
接收一或多个字符编码,然后将它们转换成一个字符串。
5.7 单体内置对象
1)、ECMA-262 对内置对象的定义是:“由ECMAScript 实现提供的、不依赖于宿主环境的对象,这些对象在ECMAScript 程序执行之前就已经存在了。”意思就是说,开发人员不必显式地实例化内置对象,因为他们已经实例化了。
5.7.1 Global对象
1)、事实上,没有全局变量或全局函数;所有在全局作用域中定义的属性和函数,都是Global 对象的属性。
2)、encodeURI()和encodeURIComponent()方法可以对URI(Uniform Resource Identifiers,通用资源标识符)进行编码。,encodeURI()主要用于整个URI,而encodeURIComponent()主要用于对URI 中的某一段进行编码。
3)、decodeURI()和decodeURIComponent()进行解码。
4)、Global对象的所有属性
属性 说明
undefined 特殊值undefined
NaN 特殊值NaN
Infinity 特殊值Infinity
Object 构造函数Object
Array 构造函数Array
Function 构造函数Function
Boolean 构造函数Boolean
String 构造函数String
Number 构造函数Number
Date 构造函数Date
RegExp 构造函数RegExp
Error 构造函数Error
EvalError 构造函数EvalError
RangeError 构造函数RangeError
ReferenceError 构造函数ReferenceError
SyntaxError 构造函数SyntaxError
TypeError 构造函数TypeError
URIError 构造函数URIError
5.7.2 Math对象
1)、min()和max()方法用于确定一组数值中的最小值和最大值。
2)、Math.ceil()、Math.floor()和Math.round()方法用于将将小数值舍入为整数。
这三种方法分别遵循的舍入规则:
Math.ceil()执行向上舍入,即它总是将数值向上舍入为最接近的整数;
Math.floor()执行向下舍入,即它总是将数值向下舍入为最接近的整数;
Math.round()执行标准舍入,即它总是将数值四舍五入为最接近的整数(这也是我们在数学课上学到的舍入规则)。
3)、Math.random()方法返回大于等于0 小于1 的一个随机数。
利用Math.random()从某个整数范围内随机选择一个值:值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能的值)。
//生成lowerValue(包括)到 upperValue(包括)的随机整数。
function selectFrom(lowerValue, upperValue) {
var choices = upperValue - lowerValue + 1;
return Math.floor(Math.random() * choices + lowerValue);
}
4)、其他方法:
Math.abs(num) 返回num的绝对值
Math.exp(num) 返回Math.E 的num 次幂
Math.log(num) 返回num 的自然对数
Math.pow(num,power) 返回num 的power 次幂
Math.sqrt(num) 返回num 的平方根
Math.acos(x) 返回x 的反余弦值
Math.asin(x) 返回x 的反正弦值
Math.atan(x) 返回x 的反正切值
Math.atan2(y,x) 返回y/x 的反正切值
Math.cos(x) 返回x 的余弦值
Math.sin(x) 返回x 的正弦值
Math.tan(x) 返回x 的正切值
六、 面向对象的程序设计
1)、ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。通俗来说对象就是一组键值对,值可以使数据或函数。
6.1 理解对象
6.1.1 属性类型
1、 数据属性
1)、数据属性包含一个数据值的位置。在这个位置可以读取和写入值。
数据属性有4 个描述其行为的特性。
[[Configurable]]:表示能否通过delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
[[Enumerable]]:表示能否通过for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
[[Writable]]:表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为true。
[[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为undefined
2)、修改属性默认的特性,必须使用ECMAScript 5 的Object.defineProperty()方法。这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。其中,描述符(descriptor)对象的属性必须是:configurable、enumerable、writable 和value。设置其中的一或多个值,可以修改对应的特性值。
2、访问器属性
1)、访问器属性包含一对儿getter 和setter 函数。
访问器属性有如下4 个特性:
[[Configurable]]:表示能否通过delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为true。
[[Enumerable]]:表示能否通过for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值为true。
[[Get]]:在读取属性时调用的函数。默认值为undefined。
[[Set]]:在写入属性时调用的函数。默认值为undefined。
2)、访问器属性不能直接定义,必须使用Object.defineProperty()来定义。
6.1.2 定义多个属性
1)、ECMAScript 5 定义了一个Object.defineProperties()方法。利用这个方法可以通过描述符一次定义多个属性。这个方法接收两个对象参数:第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应。
6.1.3 读取属性的特性
1)、使用ECMAScript 5 的Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。这个方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。返回值是一个对象,如果是访问器属性,这个对象的属性有configurable、enumerable、get 和set;如果是数据属性,这个对象的属性有configurable、enumerable、writable 和value
6.2 创建对象
6.2.1 工厂模式
1)、使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。这个模式后来被构造函数模式所取代。
6.2.2 构造函数模式
1)、构造函数模式,可以创建自定义引用类型,可以像创建内置对象实例一样使用new 操作符。不过,构造函数模式也有缺点,即它的每个成员都无法得到复用,包括函数。由于函数可以不局限于任何对象(即与对象具有松散耦合的特点),因此没有理由不在多个对象间共享函数。
2)、构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。
3)、要创建构造函数的新实例,必须使用new 操作符。以这种方式调用构造函数实际上会经历以下4个步骤:
创建一个新对象;
将构造函数的作用域赋给新对象(因此this 就指向了这个新对象);
执行构造函数中的代码(为这个新对象添加属性);
返回新对象。
4)、对象的constructor 属性最初是用来标识对象类型的。
1. 将构造函数当作函数
1)、构造函数与其他函数的唯一区别,就在于调用它们的方式不同。不过,构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数,只要通过new 操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new 操作符来调用,那它跟普通函数也不会有什么两样
2. 构造函数的问题
1)、使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。
6.2.3 原型模式
1)、创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法
2)、原型模式,使用构造函数的prototype 属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法。
1. 理解原型对象
1)、只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype 属性所在函数的指针。
2)、当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262 第5 版中管这个指针叫[[Prototype]]。虽然在脚本中没有标准的方式访问[[Prototype]],但Firefox、Safari 和Chrome 在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。
3)、虽然在所有实现中都无法访问到[[Prototype]],但可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。
栗子:Person.prototype.isPrototypeOf(person1)
4)、ECMAScript 5 增加了一个新方法,叫Object.getPrototypeOf(),在所有支持的实现中,这个方法返回[[Prototype]]的值。
栗子:Object.getPrototypeOf(person1) == Person.prototype
5)、代码读取对象属性时,搜索顺序为:对象实例本身 => 指针指向的原型对象
6)、hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。只在给定属性存在于对象实例中时,才会返回true。
7)、小技巧:in 操作符返回true 而hasOwnProperty()返回false,就可以确定属性是原型中的属性。
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
}
2. 原型与in 操作符
1)、有两种方式使用in 操作符:单独使用和在for-in 循环中使用。
在单独使用时,in 操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
2)、Object.keys()方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。
3. 更简单的原型语法
4、原型的动态性
1)、由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来——即使是先创建了实例后修改原型也照样如此。
2)、如果是重写整个原型对象,就切断了现有原型与任何之前已经存在的对象实例之间的联系。
3)、实例与原型之间的连接只不过是一个指针,而非一个副本。
4)、自己总结:先创建实例后修改原型,实例可以调用修改后原型上的方法。
如果重写原型,原实例依旧指向旧的原型,所以不能调用重写后原型上的方法。
5. 原生对象的原型
6. 原型对象的问题
1)、原型中所有属性是被很多实例共享的。对于引用类型来说,修改对象的实例中的引用类型属性的值,原型对象中的值也会改变,所以所有实例中的该引用类型属性的值都改变了。
如: function Person(){
}
Person.prototype = {
name:"张三",
friends:["李四","王五"]
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("赵六");
console.log(person1.friends);// 李四 王五 赵六
console.log(person2.friends);// 李四 王五 赵六
6.2.4 组合使用构造函数模式和原型模式
1)、构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。
栗子: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);
}
}
6.2.7 稳妥构造函数模式
1)、稳妥对象,指的是没有公共属性,而且其方法也不引用this 的对象。最适合在一些安全的环境中(这些环境中会禁止使用this 和new),或者在防止数据被其他应用程序(如Mashup程序)改动时使用。
2)、稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例方法不引用this;二是不使用new 操作符调用构造函数。
栗子:function Person(name, age, job){
//创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function(){
alert(name);
};
//返回对象
return o;
}
6.3 继承
1)、ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
6.3.1 原型链
1)、构造函数、原型和实例的关系:每个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针([[prototype]],浏览器中为_proto_属性)。
2)、继承实际上就是把一个构造函数的原型对象指向另一个类型的实例。
6.3.2 借用构造函数
1)、借用构造函数(constructor stealing)(有时候也叫做伪造对象或经典继承)基本思想:在子类型构造函数的内部调用超类型构造函数。通过使用apply()和call()方法也可以在(将来)新创建的对象上执行构造函数。
6.3.3 组合继承
1)、组合继承(combination inheritance),也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
2)、问题:组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
6.3.4 原型式继承
1)、可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。
ECMAScript 5 加入Object.create()实现原型式继承。可传入两个参数。
注:包含引用类型值的属性始终都会共享相应的值。
6.3.5 寄生式继承
1)、寄生式(parasitic)继承的思路即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象。
6.3.6 寄生组合式继承
1)、寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。是实现基于类型继承的最有效方式。
2)、寄生组合式继承的基本模式如下所示:
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
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);
this.age = age;
}
inheritPrototype(SubType, SuperType);//
SubType.prototype.sayAge = function() {
alert(this.age);
};