题记
从[我的博客]中搬运过来的,主要的目的是用于自己学习和整理思路,以及用到时查询的作用。在hexo的博客主题中,实现了目录导航定位的效果。不知道简书上怎么样?
目录
-
第三章
- 3.1 语法
- 3.4 数据类型
- 3.5 操作符
- 3.6 语句
- 3.7 函数
-
[第五章 引用类型](#第五章 引用类型)
- 5.2 [数组类型](#5.2 数组类型)
- 5.3 [Date类型](#5.3 Date类型)
-
第六章 面向对象程序设计
- 6.1 [理解对象](#6.1 理解对象)
- 6.2 [创建对象](#6.2 创建对象)
第二章 javascript简介
- netscape创建LiveScript脚本语言。搭java顺风车就改为javascript。
- js三部分:ECMAScript,dom,bom
- script标签的defer和async属性只针对外部文件,一般不用,没有这两者的时候,就会按照在页面中出现的位置先后执行的。
- js文件放在html外部的优势:可维护性,可缓存,适应未来。
- 如果浏览器不支持javascript,那么可以用noscript标签来写出替代说明文字。
第三章 基本概念
3.1 语法
- 区分大小写
- 标识符
- 注释
单行(//) 多行(/* */) - 严格模式 use strict
javascript的一种不同的解析与执行模型。一些不确定的行为将得到处理以及某些不安全的操作会抛出错误。 - 语句
3.2 关键字和保留字
全部关键字:
break,case,catct,continue,debugger,default,delete,do,else,finally,for,function,if,in,instanceof,new,return,switch,this,throw,try,typeof,var,void,while,with
3.4 数据类型
- 五个基本的数据类型:
string,number,boolean,null,undefined - 一个复杂类型:
object
3.4.1 typeof操作符
var huang = "hzhaung";
var zhuang=null;
console.log(typeof(huang)); //string
console.log(typeof(95)); //number
console.log(typeof(1 == 2)); //booleam
console.log(typeof(a)); //undefined
console.log(typeof(zhuang)); //object 原因:null是被认为是一个空的对象指针
3.4.2 Undefined类型
未申明的变量以及为初始化的变量都是undefined。
3.4.3 null类型
null值表示一个空的对象指针。
null == undefined 返回true
注意的是一般设置变量为null是为了用这个变量来保存对象,故而要写出var xx=null。
3.4.4 boolean类型
- false,"",0+NAN,null,undefined都是false。换句话说就是其他的都是true了。
- 区别大小写。
3.4.5 number类型
var num1 = 070; //八进制
var num2 = 0xA; //十六进制
var num3 = 10; //十进制
- NUMBER.MIN_VALUE或者NUMBER.MAX_VALUE超出就会自动转换成infinity,如果是负的就在前面加上-。
ifFinity( ):判断数值是否在有限数值范围内,true表示在,false表示不在。
NaN(not a number)
- 任意涉及NaN的操作都会返回NaN;
- NaN与任何值都不相等,包含NaN本身。
isNaN( ):接受参数后会将其转化成数值,不能转化的话就会返回true。
console.log(isNaN("huang")); //true
console.log(isNaN(true)); //false
console.log(isNaN(NaN)); //true
数值转换:
函数 | 作用对象 |
---|---|
Number( ) | 可用于任何数据类型 |
parseInt( ) | 针对字符串 |
parseFloat( ) | 针对字符串 |
Number的转换规则:
参数 | 结果 |
---|---|
boolean | false-0;true-1 |
number | 简单的传入换个返回 |
null | 0 |
undefined | NaN |
string | “123”-123;“1.1”-1.1;“0xf”-15;“”-0;其他-NaN) |
object | 调用valueOf( ),然后依照前面的规则 |
NaN | 调用toString( ),然后依照前面的规则 |
parseInt( )示例:
console.log(parseInt("1234blue")); //1234
console.log(parseInt("0xA")); //0
console.log(parseInt("22.5")); //22
var num1 = parseInt("10",2); //2
var num2 = parseInt("10",8); //8
var num2 = parseInt("10",10); //10
var num2 = parseInt("10",16); //16
3.4.6 String类型
- toString( )除null和undefined,因为他们没有这个方法。一般是不用参数的,可以加一个参数来表示数值的基数。
- String( )规则:null返回"null",undefined返回“undefined”。(在不知道要转化的值是不是null或者undefined)
3.4.7 Object类型
一组数据和功能的集合。每个实例具有以下的属性和方法。对象是实例的基础。
属性 | 作用 |
---|---|
Constructor | 保存用于创建当前对象的函数,构造函数 |
hasOwnproperty(propertyName) | 检查给定的属性在当前的实例中是否存在。不是在实例的原型中。 |
isPrototypeof(object) | 检查对象是否是另一个对象的原型。 |
propertyIsEnumerable | 检查是否可以用for-in来枚举。 |
toLocaleString( ) | 返回对象的字符串表示。 |
toString( ) | 返回对象的字符串表示。 |
valueOf( ) | 返回对象的字符串,数值或布尔值表示,通常和上个方法的返回值相同。 |
3.5 操作符
在对对象使用操作符的时候,要调用valueOf( )或toString( )来获得可以操作的值。
3.5.1 一元操作符
一元操作符:++,--,+,-。前置和后置的区别:前置的时候语句也改变。
递增和递减操作符的规则如下:
var str = "1";
var str1 = "z";
var b = false;
var f = 1.1;
var o = {
valueOf: function(){
return -1;
}
};
console.log(++str); //2
console.log(++str1);//NaN
console.log(++b); //1
console.log(++f);//2.1
console.log(++o);//0
//如果是对象,则会先调用valueOf(),然后根据返回的值来确定加减之后的值。
3.5.2 位操作符
32位,最后一个位表示符号位,0位正1为负。 正数就是常见的,对于负数则是以补码的形式储存(绝对值,反码,加1)。
但是输出负数时候这些操作都是隐藏的,输出的时候就是常见负数在前面加符号的形式。
var num = -18;
console.log(num.toString(2));//"-10010"
对于非数值使用位操作符,则先自动调用Number( ),然后再应用位操作。
- 按位非( ~)
返回数值的反码:操作数的负数减1。
var num = 29;
var num1 = ~ num;
console.log(num1); //-26
- 按位与
- 按位或
- 按位异或
- 左移(<<)
移出的空位就以0补充。 - 右移
3.5.3 布尔操作符
1.逻辑非
对象-false;空-true;非空-false;0-true;任意非0-false;null-true;NaN-true;undefined-true
2.逻辑与
var result = true && false;
3.逻辑或
3.5.4 乘性操作符
乘法,除法和求模
如果有一个不是数值,则会先自动调用number()之后再返回结果。
......
3.5.5 加性操作符
加法特殊:
- 两个字符串:就拼接起来。
- 只有一个字符串,则将另一个转换成字符串,然后拼接。
- 有一个是对象,数值或者布尔值,则调用toString( ),然后再拼接。
- 对于undefined和null,则调用String( )取到"undefined"和"null"。
console.log(5+"5"); //"55"
减法特殊:
不是数值的调用Number( )就OK啦。
3.5.6 关系操作符
规则:如示例代码
console.log(5 > 4);//true
console.log("h" > "z") //false 转换为字符编码比较。
console.log(5<"2") //false "2"自动转化成2
3.5.7 相等操作符
数据类型 | 说明 |
---|---|
一个boolean | 先转化成数值然后再判断 |
string+number | string转化成数值先 |
对象+非对象 | 调用对象的valueOf( )方法,得到的值再比较 |
null+undefined | 相等 |
有NaN | false |
对象+对象 | 比较是不是同一个人对象 |
全等和不全等:
- ===:未经转化的情况下相等才会返回true
null == undefined -----true
null === undefined -----false
3.5.8 条件操作符
variable = boolean_expression ? true_value : false_value ;
3.5.9 赋值操作符
3.5.10 逗号操作符
var num1,num2,num3;
3.6 语句
整理不常用的,if,for就整理啦。
3.6.2 do-while语句
只有在循环体重的代码执行之后,才会测试出口条件。即,循环体内的代码至少会执行一次。
var i = 0;
do{
i += 2;
}while(i < 10);
3.6.5 for-in 语句
一种准确的迭代语句,可以用来枚举对象的属性。
3.6.6 label语句
标签 labe:statement。这个标签可以通过break或者continue来引用,一般与for语句配合使用。
3.6.7 break和continue语句
- break会立即退出循环
- continue也会立即退出循环,但是退出后会从循环的顶部继续执行。
var no = 0;
for(var i= 1;i<10;i++){
if(i % 5 == 0){
continue;
}
no++ ;
}
console.log(no);//8
var no = 0;
for(var i= 1;i<10;i++){
if(i % 5 == 0){
break;
}
no++ ;
}
console.log(no);//4
结合label可以说明是退出的那个循环。
var num = 0;
outermost:
for(var i=0;i<10;i++){
for(var j=0;j<10;j++){
if(i == 5 && j==5){
break outermost;
}
}
}
3.6.8 with语句
将代码的作用域设置到一个特定的对象中。
with(location){
var gs = search.substring(1); //等价于var gs = location.search.substring(1);
var hostName = hostname;
var url = href;
}
3.7 函数
好处:封装,任意时刻调用。
ECMAScript中函数可以通过return语句返回值。执行return后就会停止。如果return语句不带返回的值,则会默认返回undefined。
注:eval和arguments不可以定义为变量。
函数的参数:
- 函数内部的参数是一个数组,arguments对象。 类似array,arguments[0]是合法的,而且也有length属性。故: 在定义函数的时候参数是非必须的,在调用的时候加上参数也不会出问题。这是区别其他语言的。
- arguments对象可以和命名的参数一起使用。但是两者的内存空间是不一样的。
3.7.2 没有重载
没有重载:由于js函数的特点,则是无法实现重载的,如果存在两个则后取。
第五章 引用类型
5.2 Array类型
js的数组可以保存任何类型的数据,也可以自动增长。
// 创建方法一:Array构造函数
var colors = new Array();
//创建长度是20
var colors = new Array(20);
// 创建方法二:数组字面两表示法。
var colors = ["red", "yellow", "white"];
数组不仅仅是可读的。如下:
var colors = ["red", "blue", "green"];
colors.length = 2;
alert(colors[2]); //undefined 第三项移除了
5.2.1检测数组
if(value instanceof Array){
xxx;
}
但是这种方法是有问题:
- 假定单一的全局执行环境,如果有多个框架,则就会有多个全局执行环境。从而存在多个不同版本的构造函数。
改进:isArray( )
if(Array.isArray(value)){
xxx;
}
5.2.1转换方法
转换方法 | 说明 |
---|---|
toLocaleString( ) | 类似toString( ) |
toString( ) | 会返回由数组中每个值得字符串形式凭借而成的一个以逗号分隔的字符串。 |
valueOf( ) | 返回的还是数值 |
eg:
var colors=["red","blcak","white"]
console.log(colors.valueOf());//["red", "blcak", "white"]
console.log(colors.toString());//red,blcak,white
join( ):方法讲数组按照()内的参数连接起来。
var colors=["red","blcak","white"]
var test_one = colors.valueOf().join();
var test_two = colors.valueOf().join("-");
console.log(test_one);//red,blcak,white
console.log(test_two);//red-blcak-white
5.2.3栈方法
数组可以表现的像栈一样,栈是一种可以限制插入和删除项的数据结构。后进先出。
方法 | 功能 |
---|---|
push( ) | 将参数里面的对象逐个添加到数组中,并返回数组修改后的长度值。 |
pop( ) | 与push的作用相反,但是返回值是数组的最后一项 |
var colors=["red","blcak","white"]
console.log(colors.push("huang" , "zhuang"))//5;
5.2.4 队列方法
队列的访问规则是“先进先出”,表现在队列的末端添加项,在队列的前端移除项。
方法 | 功能 |
---|---|
shift( ) | 移除数组中的第一项,并返回该项 |
unshift( ) | 与shift的作用相反,可以在数组的前端添加任意个项并返回数组的长度。 |
//模拟队列的组合
push( ) + shift( )
unshift( ) + pop( )
5.2.5 重排序方法
方法 | 功能 |
---|---|
reverse( ) | 反转数组项的排序 |
sort( ) | 按照升序的方法排列数组项,会调用每个项的toString( )转型方法,比较的是字符串 |
var values = [0, 1, 5, 10, 15];
values.sort( );
console.log(values);//[0, 1, 10, 15, 5]因为是比较的字符串,所以5在最后。
可见以上的sort( )的方法不够完美。
sort( )可以接受一个比较函数作为参数来完善不足。
function compare(value1, value2) {
return value1 - value2;
}
values.sort(compare);
console.log(values);//[0, 1, 5, 10, 15]
可见,完美解决,达到排序的效果。
5.2.6 操作方法
方法 | 功能 |
---|---|
concat( ) | 基于当前数组中的所有项创建一个新的数组(副本),然后将接受到的参数添加到这个副本的末尾。 |
slice( ) | 基于当前的数组中的一个或者多个创建一个新的数组 |
splice( ) | 向数组的中部插入项。 |
// concat
var colors = ["red", "green", "yellow"];
var color_new = colors.concat("huang",["zhuang","hz"]);
console.log(color_new);//["red", "green", "yellow", "huang", "zhuang", "hz"]
// slice
// 接受一个或者两个参数指定位置开始和到当前数组末尾的所有项。不会影响原始数组。
var colors = ["red","green","blue","yellow","purple"];
var colors2 = colors.slice(1);
console.log(colors2);//["green", "blue", "yellow", "purple"]
var colors3 = colors.slice(1,4);
console.log(colors3);//["green", "blue", "yellow"]
splice( )详解:
1.删除
可以删除任意数量的项,指定两个参数:要删除的第一项的位置和要删除的项数。会返回删除的项。返回的是一个数组。
var colors = ["red","green","blue","yellow","purple"];
var remove = colors.splice(0, 2);
console.log(colors);//["blue", "yellow", "purple"]
console.log(remove);//"red", "green"]
2.插入
可以添加任意数量的项,提供3个参数,起始位置,0和要插入的项。
var removed = colors.splice(1,0,"huang","zhuang");
console.log(colors);//["blue", "huang", "zhuang", "yellow", "purple"]
console.log(removed);//[ ]
3.替换
可以在指定的地方出入任意数量的项,同事删除任意数量的项。
var removed = colors.splice(1,1,"huang","zhuang");
console.log(colors);//["blue", "huang", "zhuang", "zhuang", "yellow", "purple"]
console.log(removed);//["huang"]
发现:
- splice( )始终会返回一个数组。
- 返回的数组包含从原始数组中删除的项,如果没有删除过,就会返回一个空数组。
5.2.7 位置方法
方法 | 功能 |
---|---|
indexOf( ) | 返回项在数组中的位置 |
lastIndexOf( ) | 同上 |
接受两个参数:第一个是要查找的项,第二个参数表示的是表示起点位置的索引。
5.2.8 迭代方法
方法 | 功能 |
---|---|
every( ) | 对数组的每一项运行给定函数,全部是true就会返回true |
filter( ) | 对数组的每一项运行给点函数,返回改函数会返回true的项组成的数组 |
foeEach( ) | 对数组的每一项运行给点函数,无返回值 |
map( ) | 对数组的每一项运行给点函数,返回每次调用函数的结果组成的数组 |
some( ) | 对数组的每一项运行给点函数,如果该函数给任一项返回true,则就会返回true |
//filter
var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.filter(function(item){
return (item >2);//[3, 4, 5, 4, 3]
});
console.log(filterResult);
//map
var numbers = [1,2,3,4,5,4,3,2,1];
var mapResult = numbers.map(function(item){
return item*2;
});
console.log(mapResult);//[2, 4, 6, 8, 10, 8, 6, 4, 2]
5.2.9 缩小方法
方法 | 功能 |
---|---|
reduce( ) | 迭代数组的所有项,然后构建一个最终返回的值,从第一项开始 |
reduceRight( ) | 迭代数组的所有项,然后构建一个最终返回的值,从最后一项开始 |
作为参数的函数接受4个参数:前一个值,当前值,项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。
var values =[1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){
return prev+cur;
});
console.log(sum);//15
5.3 Date类型
第六章 面向对象的程序设计
内容:
- 理解对象属性
- 理解并创建对象
- 理解继承
6.1 理解对象
6.1.1 属性类型
内部才有的特性,用[[...]]来表示的。
1.数据属性
属性 | 描述 |
---|---|
[[Configurable]] | 能否delete删除属性从而重新定义、能否修改属性的特性、能否把属性修改为访问器属性。 |
[[Enumerable]] | 能否通过for-in枚举。默认是true |
[[Eritable]] | 能否修改属性的值 |
[[Value]] | 包含这个属性的数据值 |
<script>
var person = {
name:"HuangZhuang";
}
</script>
如上所示:创建一个对象,它的默认内部属性前三个都是true,然后[[value]]被设置成了一个特定的值HuangZhuang。对象创建name属性,[[Value]]被设置了,在对name属性值得任何时候的修改都会反映在这个位置。
修改特性的默认值,只能调用:
Object.defineProperty(属性所在对象,"属性名字","一个描述符对象")
如果要修改,则:
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "hz"
})
console.log(person.name); //hz
person.name= "hh";
console.log(person.name); //hz
有一点注意:就是当Configurable是false时,其他三个的特性是要受到限制的,而且设定Object.defineProperty后,这四个特性均是默认设定成false的。
2.访问器属性
访问器属性不包含数组,但是包含一对getter和setter函数。
属性 | 描述 |
---|---|
[[Configurable]] | 能否delete删除属性从而重新定义、能否修改属性的特性、能否把属性修改为访问器属性。 |
[[Enumerable]] | 能否通过for-in枚举。默认是true |
[[Get]] | 读取属性时调用的函数 |
[[Set]] | 写入属性时调用的函数 |
访问器属性不可以直接定义,必须通过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;
}
}
})
book.year = 2005;
alert(book.edition); //2
下划线表示只能通过对象方法访问的属性。
6.1.2 定义多个属性
Object.defineProperties(第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应)
6.1.3 读取属性的特性
一个函数Object.getOwnPropertyDescriptor( )针对属性是数据类型还是访问器类型返回出内部属性的值。
var a = Object.getOwnPropertyDescriptor(book, "year");
console.log(a);
6.2 创建对象
Object构造函数或对象字面量都可以创建单个对象,但是这种方式是有明显的缺点的:使用同一个接口创建很多对象,会产生大量的额重复代码。
6.2.1 工厂模式
用函数来封装以特定的接口创建对象的细节。
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
}
return o;
}
var o1 = createPerson("hz", 25, 'IT');
......
但是却没有解决对象识别的问题,即怎样知道一个对象的类型。
6.2.2 构造函数模式
特点:
- 没有显示的创建对象
- 直接将属性和方法赋值给this对象
- 没有return语句
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
}
};
var Person1 = new Person("hz", 25, "IT");
构造函数始终都应该以一个大写字母开头,非构造函数才是用小写字母开头。
以上的例子中,要创建一个Person的新实例,要使用new操作符。经理四个步骤:
- 1创建一个新对象。
- 2将构造函数打的作用域赋值给新对象,this就会指向新对象。
- 3执行构造函数中的代码。
- 4返回新对象。
Person创建的实例,都有一个指向Person的Constructor属性。
console.log(Person1.constructor == Person ); //true
构造函数与其他函数的唯一区别就是调用方式。new 操作符来调用函数就是构造函数,如果没有就和普通函数没有区别的。
// 作为构造函数使用
var person = new Person("hz", 25, "IT");
person.sayName();//hz
//作为普通函数使用
Person("hz", 25, "IT");//添加到window对象
window.sayName();
// 在另一个对象中使用
var o = new Object();
Person.call(o, "hz", 25, "IT"); //在o对象的特殊作用域中调用,o就拥有了所有属性和方法。
o.sayName();
构造函数的问题就是每个方法都要在每个实例上重新创建一遍。就是说不同实例上的同名函数实际上是不一样的。如果把这些方法通过全局作用域的函数调出来可以解决,但是当需要很多方法时,封装性太差。解决方法:原型模式。
var Person1 = new Person("hz", 25, "IT");
var Person2 = new Person("hz", 25, "IT");
console.log(Person1.sayName == Person2.sayName); //false
1.理解原型对象
任何时候创建新函数,就会创建一个prototype属性,这个属性指向函数的原型对象。默认情况下,原型对象会自动获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针。
虽然没有办法访问到[[prototype]],但是通过isPrototypeOf( )方法可以确定对象之间是否存在这种关系。如果[[prototype]]指向调用isPrototypeOf( )方法的对象,就会返回true。
console.log(Person.prototype.isPrototypeOf(person1)); //true
而ECMAScript5新增加的一个方法Object.getPrototypeOf( )返回的对象实际就是这个对象的原型。Object.getPrototypeOf( )很方便的取得一个对象的原型。
console.log(Object.getPrototypeOf(person1) == Person.prototype); //true
几点说明:
- 当代码读到属性时候,就会进行一次搜索,先从实例开始,如果没有搜到,则就会继续搜索指针指向的原型。
- 实例中重新定义新的属性和方法不会改变原型中的属性和方法,但是在调用该实例的时候会屏蔽原型的。
- delete删除操作符可以完全删除实例属性。从而可以重新访问原型中的属性。
- hasOwnPrototype( )检测一个属性是否存在实例中,还是存在原型中。这个方法是继承过来的,只有在实例设定属性或者方法的时候才会返回true;
2.原型与in操作符
- 单独使用in的时候,会在对象能够访问给定属性时返回true,实例和原型均可。
console.log("name" in person1); //true
通过hasOwnPrototype( )和in结合就可以封装一个检测属性是实例还是原型中的函数:
function hasPrototypeProperty(object, name){
return !Object.hasOwnProperty(name) && (name in object);
}
原理:只要in操作符返回true而且.hasOwnProperty( )返回false就可以判断属性是存在原型中的。
- for-in循环,返回的是所有能够通过对象访问的,可枚举的属性。包括在实例中的属性以及原型中的属性。屏蔽了原型中不可枚举属性的实例属性也会返回。
Object.keys()方法可以返回所有可枚举的实例属性。参数如果是xxx.prototype则返回原型课枚举的属性。如果对于实例调用则只会返回实例的属性和方法,不会返回原型的。
如果要得到所有属性和方法,不管是不是可枚举,则使用getOwnPrototypeNames()
var keys = Object.keys(Person.prototype);
console.log(keys);//["name", "age", "job", "sayName"];
var key = Object.getOwnPropertyNames(Person.prototype);
console.log(key);//["constructor", "name", "age", "job", "sayName"];
3.更简单的原型方法
为减少不必要的代码书写,可以使用如下的方法:
function Person(){}
Person.prototype = {
constructor : Person,//一般没有,是为了重新设置constructor才用设置,但是会带来问题。
name : "hzhuang",
age : 25,
job : "IT",
sayName : function(){
console.log(this.name);
}
}
如上,Person.prototype设置成等于一个以对象字符量形式创建的新对象。但是值得注意的是此时constructor不再指向Person。此时的语法是完全重写了prototype对象,所以constructor属性就指向l构造函数Object,不再是Person了。如下:
var friend = new Person();
console.log(friend.constructor == Person);//false
console.log(friend.constructor == Object);//true
如果想重新设定回去(让friend.constructor == Person是true),那么就要在这个对象字面量里面添加一组condtructor的键值对。见3开始的例子。但是会带来问题,会让constructor属性变成可枚举的。
在兼容ECMAScript5的引擎中,可以用Object.definePeoperty来解决这个问题。
Object.defineProperty(Person.prototype, "constructor", {
enumerable : false,
value : Person
})
4.原型的动态性
对原型对象所做的任何修改都可以立即在实例上反应出来。即使是先创建实例然后修改原型。
但是如果是重写原型,情况就大不一样了。调用构造函数时会为实例添加一个执行最初原型的[[prototype]]指针,而把原型修改成另一个对象就等于切断了构造函数与最初原型的联系。实例中的指针只执行原型,不指向构造函数。
function Person(){}
var friend = new Person();
Person.prototype = {
constructor : Person,
name : "hzhuang",
age : 25,
job : "IT",
sayName : function(){
console.log(this.name);
}
}
friend.sayName();//Uncaught TypeError: friend.sayName is not a function
5.原生对象的原型
不建议修改原生对象的原型。
6.原型对象的问题
- 省略了为构造函数传递初始化参数这一环节,结果所有实例在默认的情况下都将取得相同的属性值。
- 最大的问题是有共享的本性造成的。
原型中的所有属性都可以被很多实例共享,但是对于包含引用类型的属性来说们就会出现很明显的问题。
function Person(){}
Person.prototype = {
constructor : Person,
name : "hzhuang",
age : 25,
job : "IT",
friends : ["a", "b"],
sayName : function(){
console.log(this.name);
}
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("c");
console.log(person2.friends);//["a", "b", "c"]
问题好明显,我只是修改了实例1的friends,但是却在所有的实例中反应出来了。基于此,单独的原型模式一般是不常用的。
6.2.4 组合使用构造函数模式和原型模式
- 构造函数用于定于实例属性;
- 原型模式用于定于方法和共享的属性。
重写之前有问题的例子:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["a", "b"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
console.log(this.name);
}
}
可见这种混合模式的优点:
- 每个实例都有自己的实例属性的副本,同时又有共享的方法。
- 最大限度的节省了内存
- 还支持构造函数传递参数
6.2.5 动态原型模式
把所有的信息封装在构造函数中,而通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。就是说可以通过检测某个应该存在的方法是否有效,来决定是否需要初始化原型。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["a", "b"];
if(typeof this.sayName != "function"){
Person.prototype.sayName = function(){
console.log(this.name);
}
}
}
var friend = new Person("hzhuang", 25, "IT");
friend.sayName();
if( )部分只会在初次调用函数的时候执行,之后原型就完成初始化。此时原型的修改都会反映在实例中。
6.2.6 寄生构造函数模式
创建一个函数,这个函数的作用就是封装创建对象的代码,然后再返回新创建的对象。和工厂模式一样,区别只是这里是在一个函数里面并返回的这个对象。
6.2.7 稳妥构造函数模式
- 没有公共属性。
- 也不用this对象。
function Person(name, age, job){
var o = new Object();
o.sayName = function(){
alert(name); //不使用this
};
return o;
}
除了调用sayname( )方法外,没有其他方法可以访问到传入到构造函数中的原始数据,保证了一种安全性。
6.3 继承
js没有接口继承,只支持实现继承。
6.3.1 原型链
原型链是实现继承的主要方法。
基本思路:利用原型让一个引用类型继承另一个引用类型的属性和方法。
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而每个实例都包含一个指向原型函数对象的内部指针。如果将原型对象等于另一个类型的实例。则就会形成一个链。
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());
几点说明:
- instance.constructor指向SuperType,因为SubType的原型重写了。
- instance指向SubType的原型
- SubType的原型又指向SuperType的原型
1.别忘记默认的原型
所有的函数的默认原型都是Object的实例,因此默认原型都会包含一个内部的指针,指向Object.prototype。这就是为什么函数可以继承toString( )等默认方法的根本原因。
2.确定原型和和实例的关系
两种方式:
方式 | 描述 |
---|---|
instanceof | 只要用这个操作符来测试实例与原型链中出现的构造函数,就会返回true。 |
isPrototypeOf( ) | 只要是原型链中出现过的原型,都可以说是该原生链所派生的实例的原型。 |
3.谨慎的定义方法
给原型添加方法的代码一定要放在替换原型的语句之后。
必须在用要被继承的构造函数的实例替换当前的原型之后,再定义方法。
通过原型链实现继承的时候,不能使用对象字面量创建原型方法。因为这样会重写原型链。
// 继承SuperType()
SubType.prototype = new SuperType();
SubType.prototype = {
getSubValue : function(){
return this.subproperty;
},
someOhterMethod : function(){
return false;
}
}
var instance = new SubType();
console.log(instance.getSuperValue()); //error
刚刚把SuperType的实例赋值给原型,接着又将原型替换成一个对象字面量而导致的问题。由于现在的原型包含的是一个Object的实例,而非SuperType的实例,因此我们设想的原型链已经被切断了。
4.原型链的问题
最大的问题是引用类型值的原型。还是共享问题。引用类型。
基于此,很少单独使用原型链。
6.3.2 借用构造函数
解决单独原型链中引用类型带来的问题,借用构造函数的技术。
基本思想:在子类型构造函数的内部调用超类型构造函数。函数是特定环境中执行代码的对象,可以使用apply( )和call( )方法在新创建的对象上执行构造函数。
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//继承SuperType
SuperType.call(this, "hzhaung");//hzhuang是传递的参数。
}
var instance1 = new SubType();
instance1.colors.push("blcak");
console.log(instance1.colors);//["red", "blue", "green", "blcak"]
var instance2 = new SubType();
console.log(instance2.colors);//["red", "blue", "green"]
在新SubType对象上执行SuperType( )函数中定义的所有对象初始化代码。
1.传递参数
2.借用构造函数的问题
方法在构造函数中定义,所以就无法复用。
6.3.3 组合继承
将原型链和借用构造函数的技术组合在一起。
思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样在原型上定义的方法实现了函数的复用,又可以保证每个实例有它自己的属性。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
}
function SubType(name, age){
//继承SuperType
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
console.log(this.age);
}
var instance1 = new SubType("hzhuang", 25);
instance1.colors.push("blcak");
console.log(instance1.colors);//["red", "blue", "green", "blcak"]
instance1.sayName();//hzhuang
instance1.sayAge();//25
var instance2 = new SubType("hz", 26);
instance2.sayName();//hz
instance2.sayAge();//26