原型prototype[2]

前面说了介绍了原型,这一部分说说原型的一大作用:继承。在面向对象中,继承有两种方法,分别是接口继承和实现继承。但是在JavaScript中,函数不能只签名,所以JavaScript中只有实现继承。

JavaScript继承的基本思路就是让一个引用类型继承另一个引用类型的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.getSupterValue()); //print: true

首先我们先来看下上面函数之间的关系,如下图:

上面代码的原型关系图

1 由上面的图我们可以看到,继承其实就是通过改写原型实现的。我们将SubperType的一个实例赋值给SubType.prototype,使得SubType.prototoype含有指向SuperType.prototype的指针,这样就将原型之间的关系联系起来,构成了原型链(prootype chain)。
2 注意观察,可以发现在SubType.prototype原型中含有SubType构造函数中的属性property。这是因为SubType.prototype是作为SuperType的一个实例,所以自然保存有SuperType实例中的属性,但是对于getSuperValue方法则还是保存在SuperType.prototype中。
3 SubType.prototype中含有的属性都是共享的,所以对于property属性,如果SubType有多个实例则是共享该属性值。
4 原型继承之后的原型搜索,还是和之前一样,先搜索实例对象的属性,然后在沿着原型链逐步向上搜索,如果最后还是没有找到则直接返回undefined。
5 在JavaScript中,所有的对象都是继承自Object,所以所有函数的默认原型都是Object的实例,因此默认原型都会有一个内部指针,指向Object.prototype。这也是为什么每一个函数都会有toString(), valueOf()等默认方法的基本原因。
6 对于使用原型继承的时候,不能使用字面量的形式来重写原型,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
},
someOtherMethod: function(){
return false;
}
};
var instance = new SubType();
console.log(instance.getSuperValue()); //print: error!找不到该方法

1 上面的代码首先将SubType.prototype赋值为SuperType的实例,这个时候是建立了继承关系;但是在后面又重写了SubType.prototype,这时候SubType.prototype变成了Object对象的一个实例,因此SubType和SuperType之间的关系已经被切断,两者之间没有了关系。

原型链存在的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function SuperType(){
this.color = ['red', 'blue', 'green'];
};

function SubType(){}

//继承了SuperType
SubType.prototype = new SuperType();

var instanceFirst = new SubType();
instanceFirst.color.push('black');
console.log(instanceFirst.color); //print: ['red', 'blue', 'green', 'black']

var instanceSecond = new SubType();
console.log(instanceSecond.color); //print: ['red', 'blue', 'green', 'black']

首先还是来看下函数原型之间的关系,如下图:

上面代码的原型关系图

有上面的图可以看到,由于SubType.prototype是作为SuperType的实例,所以在SubType.prototype中保存有一份SuperType实例对象都具有的属性,即color属性。这就会导致SubType的所有实例都共享一份color;
有因为在原型中额所有属性都是共享的,所以在SubType的实例中都能引用到color的属性值。因此我们可以看到在instanceFirst和instanceSecond都是共享同一个color数组,这也就是为什么instanceFirst改变了color的值之后,instanceSecond的color数组也跟着改变的原因。
原型链继承存在的问题:通过原型继承,原型实际上是变成另一个类型的实例,于是原先的实例属性也就顺理成章的变成了现在原型的属性。

原型链问题的解决方法

1 对于从SuperType构造函数继承下来的属性到原型中的问题,可以通过在SubType的构造函数中调用SuperType构造函数。可以通过call和apply来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function SuperType(){
this.colors = ['red', 'blue', 'green'];
};
function SubType(){
//继承SuperType
//在this的作用域上调用SuperType构造函数
SuperType.call(this);
};

var instanceFirst = new SubType();
instanceFirst.colors.push('black');
console.log(instanceFirst.colors); //print: ['red', 'blue', 'green', 'black']

var instanceSecond = new SubType();
console.log(instanceSecond.colors); //print: ['red', 'blue', 'green']

上面的这种方法通常称之为经典继承方法,但是该方法还是存在构造函数模式的缺点,即所有的方法都在构造函数中定义,因此函数的复用就无从谈起。因此很多情况下都是使用下面的一种方法:组合继承。

2 组合继承即在上面的额方法中添加如原型链的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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(){
console.log(this.age);
};

var instanceFirst = new SubType("Louis", 23);
instanceFirst.colors.push('black');
console.log(instanceFirst.colors); //print: ['red', 'blue', 'green', 'black']
console.log(instanceFirst.sayName()); //print: Louis
console.log(instanceFirst.sayAge()); //print: 23

var instanceSecond = new SubType("June", 21);
console.log(instanceSecond.colors); //print: ['red', 'blue', 'green']
console.log(instanceSecond.sayName()); //print: June
console.log(instanceSecond.sayAge()); //print:21

这种方法避免了原型链和经典继承的弊端,因此较为常用。

Array数组系列

[1]数组的介绍
[2]数组API[1]
[3]数组API[2]
[4]数组API[3]
[5]数组API[4]

参考文章

[1]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
[2]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Array_comprehensions
[3]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype
[4]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length
[5]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
[Recommend]http://web.mit.edu/jwalden/www/isArray.html

[1]数组的介绍

  1. 数组创建语法

    • 数组创建有三种方法,语法分别如下:

      1
      2
      3
      4
      5
      6
      //第一种
      [element0, element1, ..., elementN]
      //第二种
      new Array(element0, element1[, ...[, elementN]])
      //第三种
      new Array(arrayLength)

      elementN

      1. 前两种方法通过传入参数对数组进行初始化,当然参数也可以为空,直接是:
      1
      2
      var arrTemp1 = [];
      var arrTemp = new Array();

      需要注意的是,如果使用第二种方法创建数组,当传入的参数中有一个且为数值的时候,则变成用第三种方法创建数组了。

      arrayLength

      1. 数组创建的第三种方法是指定数组的长度,数组创建后每个元素都为undefined,长度则为传入的数值。
      • JavaScript的数组类似与列表形式,数组中的元素类型和数组的长度都可以发生改变。
  2. 数组的访问

  • 数组的第一个元素的下标为0,最后一个元素的下标则为数组的长度减去1(arr.length - 1)。
1
2
3
4
var arr = ['this is the first element', 'this is the second element'];
console.log(arr[0]); //打印:'this is the first element'
console.log(arr[1]); //打印:'this is the second element'
console.log(arr[arr.length - 1]); //打印:'this is the second element'

Tip:
数组的访问不能类似对象Object访问属性一样使用点(.)。其实无论是数组还是对象,在访问元素或者属性的时候,如果属性开头是数字,在进行访问的时候都只能通过方括号进行[]。

1
2
3
4
5
6
var temp = {
'3d' : '3dModel',
'2d' : '2dModel'
}
console.log(temp.3d) //报错 SyntaxError: Unexpected identifier(…)
console.log(temp[3d]) //打印:3dModel
  1. 数组长度length和下标index的关系
  • 数组的长度通过arr.length获得,下标的范围则是从0开始到arr.length - 1
1
2
3
var fruits = [];
fruits.push("banana", 'apple', 'peach');
console.log(fruits.length); //打印: 3
  • 通过改变数组的长度来修改数组内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fruits[5] = 'mango';
console.log(fruits[5]); //打印:mango
console.log(Object.keys(fruits)); //打印:["0", "1", "2", "5"]
console.log(fruits.length); //打印:6
console.log(fruits); //打印:["banana", "apple", "peach", undefined × 2, "mango"]

//增加数组的长度
fruits.length = 10;
console.log(fruits.length); //打印:10
console.log(fruits); //打印:["banana", "apple", "peach", undefined × 2, "mango", undefined × 4]

//减少数组的长度
fruits.length = 2;
console.log(fruits.length); //打印:2
console.log(fruits); //打印:["banana", "apple"]

结论:

  1. 改变数组的长度会影响数组元素中的内容
  2. 增加数组的长度,对于新增的数组元素内容直接赋值为undefined
  3. 减少数组的长度,则截断数组
  1. 数组常用API分类
    *. 数组属性(properties)

Array.length
Array.prototype

*. 改变数组API(Mutator Methods)

Array.prototype.pop()
Array.prototype.push()
Array.prototype.shift()
Array.prototype.unshift()
Array.prototype.reverse()
Array.prototype.sort()
Array.prototype.splice()

*. 访问数组API(Accessor Methods)

Array.prototype.concat()
Array.prototype.join()
Array.prototype.slice()
Array.prototype.toString()
Array.prototype.indexOf()
Array.prototype.lastIndexOf()

*. 遍历数组API(Iteration Methods)

Array.prototype.forEach()
Array.prototype.every()
Array.prototype.some()
Array.prototype.filter()
Array.prototype.map()
Array.prototype.reduce()
Array.prototype.reduceRight()

[2]数组API[1]

Array.prototype

Array.prototype属性代表了数组Array构造器的原型链。如果需要自行拓展数组函数,需要将函数指向Array.prototype链上。
使用Array.isArray(Array.prototype)返回的结果为true

Array.prototype.length

获取数组的长度:arr.length;数组的最大长度为2的32次方,即Math.pow(2,32)

1
2
var temp = new Array(5);
console.log(temp.length); //打印:5

常用于遍历数组的时候,提供退出条件

1
2
3
4
var numbers = [1,2,3,4,5];
for(var i = 0; i < numbers.length; ++i){
numbers[i] *= 2;
}

Array.isArray()

用于判断一个对象是否为数组,如果是则返回true,否则返回false。
语法:Array.isArray(obj)

1
2
3
4
5
6
7
8
9
10
//以下都是返回true
Array.isArray([]);
Array.isArray(Array.prototype);
Array.isArray(new Array());

//以下都是返回false
Array.isArray();
Array.isArray({});
Array.isArray(null);
Array.isArray(undefined);

Array.isArray()只在IE9及其之上版本才支持,对于需要在IE9以下的版本,这需要使用以下兼容性代码:

1
2
3
4
5
if(!Array.isArray){
Array.isArray = function(arg){
return Object.prototype.toString.call(arg) === '[object Array]';
}
}

typeof VS. instanceof

  1. typeof操作符

语法:typeof operand
参数:operand是一个表达式,表示对象或原始值
常见的类型及其返回值,如下:

常见类型及其返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//返回"number"
typeof 37
typeof 3.14
typeof Math.LN2
typeof Infinity
typeof NaN //NaN是"Not-A-Number"的缩写,意思是不是一个数字,其返回结果为:"number"
typeof Number(1) //不建议这么使用

//返回"string"
typeof ""
typeof "bla"
typeof (typeof 1)
typeof String("Louis") //不建议这么使用

//返回"boolean"
typeof true
typeof false
typeof Boolean(true) //不建议这么使用

//返回"undefined"
typeof undefined
typeof blabla //对于没有定义的变量,自然是Undefined类型;对于一个定义了,但是没有赋值的变量也为Undefined

//返回"object"
typeof {a: 1}
typeof new Date()
//下面的三种用法不建议,容易和上面的发生混淆
typeof new String("Louis")
typeof new Number(21)
typeof new Boolean(true)

//返回"function"
typeof function(){}
typeof Math.sin

  1. instanceof操作符

语法:object instanceof constructor
参数:object需要检测的对象; constructor构造函数
instanceof是用于检测对象object是否在构造函数constructor原型属性(prototype property)的原型链(prototype chain)上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//定义构造函数
function C(){}
function D(){}

var objC = new C();
objC instanceof C; //true
objD instanceof D; //false
objC instanceof Object; //true, 通过继承
C.prototype insanceof Object; //true

C.prototype = {};
var objC2 = new C();
objC2 instanceof C; //true
objC instanceof C; //true,因为原型已经改写,切断了原来实例与原型最初的关系

[3]数组API[2]

Mutator Methods

1 这个部分介绍的API主要有以下几个:

Array.prototype.pop()
Array.prototype.push()
Array.prototype.shift()
Array.prototype.unshift()
Array.prototype.reverse()
Array.prototype.sort()
Array.prototype.splice()

*上面所列举的数组API都会改变当前操作的数组内容。

Array.prototype.pop()

该方法删除数组的最后一个元素,并且返回被删除的元素。[在原来的数组上操作,且有返回值]
语法:arr.pop();

1
2
3
4
5
6
7
var arr = [1,2,3,4];
var temp = arr.pop();
console.log(temp); //print: 4
console.log(arr); //print: [1,2,3]

//对于一个空数组,pop元素会返回undefined
console.log([].pop()); //print: undefined

Array.prototype.push()

该方法将一个或者多个元素插入到数组的最后位置,并且返回新数组的长度。[在原来的数组上操作,并且有返回值]
语法:arr.push(element1[, element2, … , elementN]);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var sports = ['soccer', 'baseball'];
var total = sports.push('football', 'swimming');

console.log(sports); //print: ['soccer', 'baseball', 'football', 'swimming']
console.log(total); //print: 4

//合并两个数组比较
var vegetables = ['parsnip', 'potato'];
var moreVegs = ['celery', 'beetroot'];
console.log(vegetables.push(moreVegs)); //print: ['parsnip', 'potato', Array[2]]

//使用apply合并两个数组
Array.prototype.push.apply(vegetables, moreVegs);
console.log(vegetables); //print: ['parsnip', 'potato', 'celery', 'beetroot']

Array.prototype.shift()

该方法将数组的第一个元素移除,并且返回被移除的数组。[在原来的数组上操作,并且有返回值]
语法:arr.shift();

1
2
3
4
5
6
7
8
var myFish = ['angel', 'clown', 'mandarin', 'surgeon'];
console.log('myFish before: ' + myFish); //print: myFish before: ["angel", "clown", "mandarin", "surgeon"]
var shifted = myFish.shift();
console.log('myFish after: ' + myFish); //print: myFish after: ["clown", "mandarin", "surgeon"]
console.log('Removed this element: ' + shifted); //print: Removed this element: angel

//对于一个空数组,shift会返回undefined
console.log([].shift()); //print: Undefined

Array.prototype.unshift()

该方法将一个或者多个元素插入到数组的第一个位置,并且返回新数组的长度。[在原来的数组上操作,并且有返回值]
语法:arr.unshift(element1[, element2, …, elementN]);

1
2
3
4
5
6
7
var arr = [1, 2];
arr.unshift(0); // result of call is 3, the new array length; arr is [0, 1, 2]
arr.unshift(-2, -1); // = 5; arr is [-2, -1, 0, 1, 2]
arr.unshift([-3]); // arr is [[-3], -2, -1, 0, 1, 2]

//合并两个数组
Array.prototype.unshift.apply([1,2], [3,4]); //the new arr is [1,2,3,4]

Array.prototype.reverse()

该方法将数组中所有元素的位置进行反转。[在原来的数组上操作,并且有返回值]
语法:arr.reverse()

1
2
3
var myArray = ['one', 'two', 'three'];
myArray.reverse();
console.log(myArray) //print: ['three', 'two', 'one']

Array.prototype.sort()

该方法对数组中所有的元素都进行排序,如果没有提供比较函数compareFunction,则按照字符串的Unicode码的顺序进行排序。[在原来的数组上操作,并且有返回值]
语法:arr.sort([compareFunction])

1
2
3
4
5
6
7
8
9
//比较函数没有提供的情况下:
var fruit = ['cherries', 'apples', 'bananas'];
fruit.sort(); //['apples', 'bananas', 'cherries'];

var scores = [1, 10, 2, 21];
scores.sort(); //[1, 10, 2, 21] //没有提供比较函数,都是转化成字符串,然后根据字符串的Unicode码进行排序

var things = ['word', 'Word', '1 Word', '2 Words'];
things.sort(); //['1 Word', '2 Words', 'Word', 'word']

compareFunction(a, b)比较函数

  1. 注意:a,b分别代表数组中比较的两个元素,可以是数值、对象、数组等
  2. 如果compareFunction(a, b)返回的数值小于0,则a排在b的前面
  3. 如果compareFunction(a, b)返回的数值为0,则不对a和b进行交换位置。需要注意的是ESMAscript并没有对此行为做出保证,不同的浏览器可能会有不同的做法。
  4. 如果compareFunction(a, b)返回的数值大于0,则a排在b的后面
    compareFunction(a, b)常见的形式如下:
1
2
3
4
5
6
7
8
9
10
function compare(a, b){
if(a is less than b by some ordering criterion){
return -1;
}
if(a is greater than b by the ordering criterion){
return 1;
}
//a must be equal to b
return 0;
}

Example:

1
//按数值大小排序
var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b){
	return a - b;
});
console.log(numbers);			//print: [1, 2, 3, 4, 5]

//对于对象也可以按照其中的属性值进行排序
var items = [
 { name: 'Edward', value: 21 },
 { name: 'Sharpe', value: 37 },
 { name: 'And', value: 45 },
 { name: 'The', value: -12 },
 { name: 'Magnetic' },
 { name: 'Zeros', value: 37 }
];
items.sort(function(a, b){
	if(a.value > b.value){
		return 1;
	}
	if(a.value < b.value){
		return -1;
	}
	return 0;
});
console.log(items);
//-12, 21, 37, 45, undefined, 37; 因为itesms[4]中的对象没有value属性,无法进行比较,所以前面的4个是按顺序排好,最后两个还是原来的顺序。

字符串与数值运算小结:
console.log(+true); //print: 1;
console.log(+false); //print: 0;
‘90’ - 80; //results: 10;
‘a’ - ‘b’; //results: NaN;
+’90’; //results: 90;
数值型字符串会先转换成数值再进行运算;而对于非数值型字符串由于无法转换成数值,所以做任何四则运算结果都是NaN

Array.prototype.splice()

该方法可以对数组中已经存在元算进行删除,也可以添加元素到数组中。[在原来的数组上操作,并且有返回值]
语法:arr.splice(startIndex, deleteCount[, element1, element2, …, elementN])
startIndex: 改变数组的起始位置,如果startIndex大于数组的长度,则将数组的长度赋值给startIndex;如果startIndex为负数,则加上数组的长度。
deleteCount: 从startIndex开始,要删除元素的个数;如果deleteCount为0,则不删除数组中的元素,但是此时需要提供至少一个新的元素(即第三个参数);如果deleteCount的数值大于startIndex后面元素的个数,则将从startIndex处开始,将数组后面的元素全部删除。
elementN: 要添加到数组中的元素,如果该参数为空则splice()值做删除操作。
return:返回值是被删除的元素,如果没有删除操作则返回空数组。

1
2
3
4
5
6
7
8
9
10
11
12
var myFish = ['angel', 'clown', 'mandarin', 'surgeon'];
var removed = myFish.splice(2, 0, 'drum'); //myFish is ['angel', 'clown', 'drum', 'mandarin', 'surgeon']
// removed is [], no elements removed
removed = myFish.splice(3, 1); // myFish is ['angel', 'clown', 'drum', 'surgeon']
// removed is ['mandarin']
removed = myFish.splice(2, 1, 'trumpet'); // myFish is ['angel', 'clown', 'trumpet', 'surgeon']
// removed is ['drum']
removed = myFish.splice(0, 2, 'parrot', 'anemone', 'blue');
// myFish is ['parrot', 'anemone', 'blue', 'trumpet', 'surgeon']
// removed is ['angel', 'clown']
removed = myFish.splice(3, myFish.length); // myFish is ['parrot', 'anemone', 'blue']
// removed is ['trumpet', 'surgeon']

[4]数组API[3]

Accessor Methods

1 这部分将要介绍的API主要是以下几个:

Array.prototype.concat()
Array.prototype.join()
Array.prototype.slice()
Array.prototype.toString()
Array.prototype.indexOf()
Array.prototype.lastIndexOf()

*上面列举的API都不会修改原来的数组

Array.prototype.concat

由原来的数组和提供的参数组成一个新的数组,并返回。
语法:var newArr = oldArr.concat([element1, element2, element3, …, elementN]);
说明:

1 concat()函数并不会改变原来数组中的元素,但是会返回一个新的数组。
2 对于数组中含有对象引用,concat()函数只是进行浅复制(shallow copy),即新数组和原来数组都是指向同一个对象。也就是说,一旦指向的对象被修改,则原来数组和新数组中相应的元素都会发生变化。
3 对于字符串和数值则是直接复制到新数组中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var alpha = ['a', 'b', 'c'],
numeric = [1, 2, 3];
var alphaNumeric = alpha.concat(numeric);
console.log(alphaNumeric); //result: ['a', 'b', 'c', 1, 2, 3]

var num1 = [1, 2, 3],
num2 = [4, 5, 6],
num3 = [7, 8, 9];
var nums = num1.concat(num2, num3);
console.log(nums); //result: [1, 2, 3, 4, 5, 6, 7, 8, 9]

//指向对象引用
var obj = {name: "Louis", age: 23};
var objArr = [obj];
var newArr = objArr.concat();
console.log(objArr[0].age); //result: 23
console.log(newArr[0].age); //result: 23

obj.age = 24;
console.log(objArr[0].age); //result: 24
console.log(newArr[0].age); //result: 24

Array.prototype.join()

该方法将数组转换成字符串,并返回该字符串。
语法:arr.join([separator=’,’])
separator: 分隔符是可选的,当没有传入分隔符的时候则数组返回的字符串中每个元素使用逗号, 进行分开;如果有传入分隔符,则按照分隔符分开。

1
2
3
4
5
var a = ['Wind', 'Rain', 'Fire'];
var myVar1 = a.join(); // assigns 'Wind,Rain,Fire' to myVar1
var myVar2 = a.join(', '); // assigns 'Wind, Rain, Fire' to myVar2
var myVar3 = a.join(' + '); // assigns 'Wind + Rain + Fire' to myVar3
var myVar4 = a.join(''); // assigns 'WindRainFire' to myVar4

Array.prototype.slice()

该方法返回一个新的数组,新的数组是原来数组的部分浅复制(shadllow copy)。
语法:arr.slice([startIndex[, endIndex]])
startIndex: 当没有传入该参数则默认为0;如果传入的是负数,则加上数组长度;
endIndex: 当没有传入该参数则默认是数组的长度(arr.length);如果传入的是负数,则加上数组的长度
需要注意的是截取的数组中不包括endIndex位置处的元素,即从startIndex到endIndex-1
对于对象引用,跟Array.prototype.concat()函数一样

1
2
var fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
var citrus = fruits.slice(1, 3); // citrus = ['Orange','Lemon']

Array.prototype.toString()

将数组组成一个字符串,并返回字符串。
语法:arr.toString()
与arr.join();得到的字符串一样

1
2
3
var fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
var temp = fruits.toString(); //print: "Banana,Orange,Lemon,Apple,Mango"
var temp2 = fruits.join(); //print: "Banana,Orange,Lemon,Apple,Mango"

Array.prototype.indexOf()

该方法从数组(由前往后,即从0到arr.length-1顺序)找查找某个元素,如果能找到则返回第一个相同元素在数组中的位置;如果没能找到则返回-1
语法:arr.indexOf(searchElement[, fromIndex = 0])
searchElement: 要在数组中查找的元素
fromIndex: 从某个位置开始查找,如果没有该参数则默认为0;如果fromIndex大于或者等于数组的长度,则直接返回-1;如果fromIndex小于0,则加上数组的长度,从结果值的位置开始查找,如果结果值还是小于0,则是整个数组查找。

1
2
3
4
5
6
7
var array = [2, 5, 9];
array.indexOf(2); // 0
array.indexOf(7); // -1
array.indexOf(9, 2); // 2
array.indexOf(9, 3); //-1
array.indexOf(2, -1); // -1
array.indexOf(2, -3); // 0
1
2
3
4
5
6
7
8
9
10
11
//找到数组中某个元素的所有位置
var array = ['a', 'b', 'a', 'c', 'a', 'd'];
var searchElement = 'a';

var indices = [];
var index = array.indexOf(searchElement);
while(index != -1){
indices.push(index);
index = array.indexOf(searchElement, index + 1);
}
console.log(indices);

indexOf()只在IE9及其以上支持,所以对于低版本的IE需要先实现indexOf()函数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
if(!Array.prototype.indexOf){
Array.prototype.indexOf = function(searchElement, fromIndex){
var k;
//this是指向调用的函数
//只有当this === undefined的时候才会 === void 0
if(this == null || this === void 0){
throw new TypeError('"this" is null or not defined');
}

//将函数转化成对象
var O = Object(this);

//>>>的两个作用:1)将非数值型转化成0;2)对于含有小数点的,只取其整数部分
var len = O.length >>> 0;

if(len === 0){
return -1;
}

//根据是否传入fromIndex进行取值
var n = +fromIndex || 0
if(Math.abs(n) === Infinity){
n = 0;
}
if(n >= len){
return -1;
}

//k其实是数组下标
k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
//逐个数组元素进行比较
while( k < len ){
if(k in O && O[k] === searchElement){
return k;
}
K++;
}
return -1;
}
}

Array.prototype.lastIndexOf()

从数组中查找指定的元素,如果存在则返回该元素第一次出现在数组中的位置,否则返回-1。需要注意的是查找的方向是从数组尾部往前,即arr.length到0
语法:arr.lastIndexOf(searchElement[, fromIndex = arr.length - 1])
searchElement: 要查找的值
fromIndex: 如果没有传入该参数则默认为arr.length数组长度;如果传入的数值大于或等于数组的长度,则对整个数组都进行查找;如果传入的是负数,则加上数组的长度,从结果值处开始查找,如果结果值还是负数则直接返回-1;需要注意的是对于有传入fromIndex,是从fromIndex处开始往前查找

1
2
3
4
5
6
7
var array = [2, 5, 9, 2];
array.lastIndexOf(2); // 3
array.lastIndexOf(7); // -1
array.lastIndexOf(2, 3); // 3
array.lastIndexOf(2, 2); // 0 注意结果不是 3
array.lastIndexOf(2, -2); // 0
array.lastIndexOf(2, -1); // 3

[5]数组API[4]

Iteration Methods

1 这部分将要介绍的API主要是以下几个:

Array.prototype.forEach()
Array.prototype.every()
Array.prototype.some()
Array.prototype.filter()
Array.prototype.map()
Array.prototype.reduce()
Array.prototype.reduceRight()

*上面列举的API都是用于遍历数组,这一部分的API比较容易混淆,需要理解清楚。

Array.prototype.forEach()

Array.prototype.every()

Array.prototype.some()

Array.prototype.filter()

####Array.prototype.map()

####Array.prototype.reduce()

####Array.prototype.reduceRight()

在看了不少资料之后,对于原型的理解就是两个字,共享。
在这一部分,将通过分析原型出现的原因,已经原型的应用–>继承。

原型Prototype的出现

在讨论原型的出现,就需要从对象的创建开始说起。

创建对象可以通过new Object()或者直接通过对象字面量{}完成,但是使用这种方法会导致在创建多个对象的时候,产生大量的代码。因此有了工厂模式:

  1. 工厂模式
    工厂模式就是使用一个函数将需要创建的对象的属性都封装起来,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function createPerson(name, age, job){
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.job = job;
    obj.alertName = function(){
    console.log(this.name);
    }
    return obj;
    }

    var personFirst = createPerson("June", 21, "Student");
    var PersonSecond = createPerson("Louis", 23, "Student");

    这样就可以通过工厂模式创建对象,只需要将参数传入到createPerson函数中,函数会返回一个obj初始化后的对象。需要注意的是,使用工厂模式创建的对象,不能解决对象识别问题,即:

    1
    2
    personFirst instanceof object 	//打印true
    personFirst instanceof createPerson //打印false
  2. 构造函数模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.alertName = function(){
    console.log(this.name);
    }
    }

    var personFirst = new Person("June", 21, "Student");
    var personSecond = new Person("Louis", 23, "Student");

1.首先构造函数和普通的函数是没有什么不同
2.要把一个函数当成构造函数使用,只需要使用new操作符
3.根据习惯,构造函数的第一个字母使用大写

  • new操作符创建对象的过程
    创建一个新的对象 –> 将构造函数的作用域赋给新对象(即this指向了新对象) –> 对象的初始化 –> 返回新对象

  • 构造函数模式的不足

使用构造函数存在的最大问题就是每个实例都会将所有的属性创建一次。这个对于数值属性来说可以接受,但是如果函数方法每个实例都要创建一遍,则不合理。所以对于构造函数模式可以将方法转移到构造函数外部,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}

function sayName(){
console.log(this.name);
}

var personFirst = new Person("June", 21, "Student");
var personSecond = new Person("Louis", 23, "Student");

但是对于这种方法,如果需要多个函数,则会导致很多个全局函数。对于构造函数模式的这个问题,我们可以通过原型来解决。

  1. 原型模式
    • 原型

在每个创建的函数中都会有一个原型prototype属性,这个属性是一个指针,指向一个对象。原型指向的这个对象包含可以由该函数创建的对象共享访问的属性和方法。简单的说就是该原型指向的对象中含有可以被共享访问的属性和方法,而只有该函数创建的实例才有权限访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//构造函数
function Person(){}
//构造函数prototype指向的原型对象
Person.prototype.name = "Louis";
Person.prototype.age = 23;
Person.prototype.job = "Student";
Person.prototype.sayName = function(){
console.log(this.name);
}
//使用构造函数创建的实例
var personFirst = new Person();
personFirst.sayName();

var personSecond = new Person();
personSecond.sayName();

console.log(personFirst.sayName == personSecond.sayName); //打印true

我们在Person函数的原型中添加的所有属性和方法都能够被personFrist和personSecond访问,而且需要注意的是共享访问,而不是各自保存一个副本。

  • 原型对象的理解
    1. 在创建了一个函数之后,JavaScript引擎就会为该函数创建一个prototype属性,这个属性指向原型对象。而原型对象中含有一个constructor的属性,这个属性指向含有原型对象所在的函数。就如之前的构造函数Person、Person.prototype、personFirst之间的关系如下图:
      三者之间的关系示意图

由上图可以看到,其实实例和构造函数之间并没有直接的联系。每个实例中都含有[[prototype]]属性,这个属性指向Person.prototype这个原型对象,而Person.prototype原型对象中含有constructor属性则指向构造函数Person,构造函数也含有指向Person.prototype原型的属性prototype。
在personFirst中我们可以看到,该对象除了[[prototype]]属性之外,并没有其他的属性。但是我们能够通过personFirst.name得到“Louis”的值。这就是原型和对象实例查找属性的问题:

一个对象在读取某个属性值的时候,首先是先查找该对象是否包含这个属性,如果有则返回,不再向原型查找;如果没有则向上查找该对象指向的原型是否含有这个属性,如果有则返回原型中该属性的值,如果没有则继续向继承的原型链上查找,如果还是没有则直接返回undefined。
对于上面的例子,在使用personFirst.name访问取name属性值的时候,首先先找personFirst这个对象上是否含有属性值name,很明显并没有,接着则personFirst的原型链Person.prototype上查找name属性,发现有,而且值为”Louis”,则将该值直接返回。

  1. 原型属性的屏蔽:当我们在对象实例上创建一个和原型上相同名称的属性时,根据属性的查找顺序,我们可以知道原型上相同名称的属性将会被屏蔽掉。Show the fllowing code!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     //构造函数
    function Person(){}
    //原型对象
    Person.prototype.name = "Louis";
    Perosn.prototype.age = 23;
    Person.prototype.job = "Student";
    Person.prototype.sayName = function(){
    console.log(this.name);
    }
    //实例
    var personFirst = new Person();
    personFirst.name = "John";
    personFirst.sayName() //打印: John --> 来自实例

    var personSecond = new Person();
    personSecond.sayName() //打印:Louis --> 来自原型

当我们为对象添加一个属性的时候,这个属性就会屏蔽原型对象中的同名属性;也就是说如果添加的这个属性只会阻止我们访问原型中的那个对象,但是不会修改原型中的属性。不过,我们可以使用delete操作符来删除对象实例属性,从而恢复访问原型中的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(){}
Person.prototype.name = "Louis";
Person.prototype.age = 23;
Person.prototype.job = "Student";
Person.prototype.sayName = function(){
console.log(this.name);
}

var person1 = new Person();
var person2 = new Person();

person1.name = "June";
console.log(person1.name); //打印: June --> 来自对象
console.log(person2.name); //打印: Louis--> 来自原型

delete person1.name;
console.log(person1.name); //打印: Louis--> 来自原型
  • in操作符
    1. hasOwnProperty && in 操作符
      使用hasOwnProperty()方法可以检测是一个属性是存在于实例中,还是原型中。只有当给定的属性存在于对象实例中,才会返回true。而对于in操作符,则无论属性是否存在于实例还是原型中,只要能够访问到这个属性,则返回true。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//使用hasOwnProperty()方法
function Person(){};
Person.prototype.name = "Louis";
Person.prototype.age = 23;
Person.prototype.job = "Student";
Person.prototype.sayName = function(){
console.log(this.name);
}

var personFirst = new Person();
var personSecond = new Person();

console.log(personFirst.hasOwnProperty("name")); //false --> 来自原型
console.log(personSecond.hasOwnProperty("age")); //false --> 来自原型

personFirst.name = "June";
console.log(personFirst.hasOwnProperty("name")); //true --> 来自对象实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
	//使用in操作符
function Person(){};
Person.prototype.name = "Louis";
Person.prototype.age = 23;
Person.prototype.job = "Student";
Person.prototype.sayName = function(){
console.log(this.name);
}

var personFirst = new Person();
var personSecond = new Person();

console.log("name" in personFirst); //true --> 来自原型
console.log("age" in personSecond); //true --> 来自原型

personFirst.name = "June";
console.log("name" in personFirst); //true --> 来自实例对象
```

> 下面的代码用来检测属性是否存在于原型中

```JavaScript

function hasPrototypeProperty(obj, name){
return !obj.hasOwnProperty(name) && (name in obj);
}
  • 原型的字面量写法
    1. 上面使用Person.prototype.xxx的方法书写原型,每次需要添加属性的时候都要重复书写前面部分的代码。对于这种情况,我们可以使用原型的字面量来创建原型对象。
1
2
3
4
5
6
7
function Person(){}
Person.prototype = {
name: "Louis",
age : 23,
job : "Student",
sayName : function(){console.log(this.name);}
}

使用原型字面量有一点需要注意的是,原型对象的constructor属性不再指向原来的构造函数Person();
这是因为使用Person.prototype = {}创建原型,相当于Person.prototype是Object的实例,而实例中存在的[[prototype]]指向的是Object的原型,原型中的constructor属性指向的就是Object对象,所以如果constructor属性很重要,则需要在创建原型的字面量上重新指定,如:

1
2
3
4
5
6
7
8
function Person(){}
Person.prototype = {
constructor : Person,
name: "Louis",
age : 23,
job : "Student",
sayName : function(){console.log(this.name);}
}
  • 原型的动态性
    1. 原型的动态性体现在只要对原型对象所做的任何修改都能够立即从实例上得到反映。
1
2
3
4
5
var friend = new Person();
Person.prototype.sayHi = function(){
console.log("halo Louis");
}
friend.sayHi(); //打印: halo Louis

需要注意的是,给原型添加属性和方法都能够在实例上得到反映,但是如果重新改写原型,则会切断实例和原型之间最初的关系。
需要谨记的是:实例中的指针仅仅指向原型,而不是指向构造函数
重写原型之后构造函数、对象实例、原型之间的关系如下图:

重写原型之后构造函数、对象实例、原型之间的关系

  • 原型的弊端
    1.原型的弊端也是显而易见的,对于数值型属性而言,每一个属性也都是共享的。对于所创建的每一个实例都具有相同的属性值,其实也不合理。应该是数值型属性为各自所有,而方法属性则可以进行共享,所以一般在创建构造函数的时候都是使用构造函数模式和原型模式相结合进行,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//属性私有
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
//方法共有
Person.prototype.sayName = function(){
console.log(this.name);
}
var personFirst = new Person("June", 21, "Student");
var personSecond = new Person("Louis", 23, "Student");
personFirst.sayName(); //打印: June
personSecond.sayName(); //打印: Louis

参考文章

[1]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
[2]http://yehudakatz.com/2011/08/12/understanding-prototypes-in-javascript/
[3]http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html
[4]http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
[5]JavaScript高级程序设计(3nd Edition)第六章

判断浏览器

IE浏览器的判断

  • 总体判断IE

    var IE = !!window.ActiveXObject || “ActiveXObject” in window (判断包括IE11)。

  • 分别判断IE6,IE7,IE8

    var appVersion,
    isIE6,
    isIE7,
    isIE8;
    if( navigator.appName === “Microsoft Internet Explorer” ){
    appVersion = navigator.appVersion.split(“;”)[1].replace(/[ ]/g, “”);
    isIE6 = appVersion === “MSIE6.0”;
    isIE7 = appVersion === “MSIE7.0”;
    isIE8 = appVersion === “MSIE8.0”;
    }

火狐浏览器的判断

var isFirefox = navigator.userAgent.indexOf(“Firefox”) > 0;

[5]跨浏览器中事件

  1. 做前端兼容性是个躲不开的话题,当你写出来的代码兼容性越强说明你的功底越深。所以在这里探讨下事件event的兼容性问题。event兼容性问题主要是IE浏览器与其他浏览器事件处理的区别,有如下几个方面需要了解:
  1. IE事件模型
  2. 事件的兼容性写法

IE事件模型

  1. IE支持的事件是中间模型,这个模型有区别与DOM Level 0 Events和DOM Level 2 Events。IE事件模型包括Event对象,但event并不会传递给事件句柄函数,而是作为Window对象的属性。另外IE事件模型不支持捕获形式Capturing的事件传播形式,只支持冒泡Bubbling事件传播。
  • IE Event对象

属性如下:

  1. type(String),声明事件的类型。该属性的值是删除前缀on的事件名称,如click、mouseover;与DOM Event对象的type属性兼容。
  2. srcElement(node), 发生事件的文档元素。与DOM Event对象的target属性兼容。
  3. cancelBubble(boolean), IE Event对象没有stopPropagation()函数,所以如果需要阻止事件冒泡则需要通过设置cancelBubble的值,为true的时候表示阻止当前事件冒泡。
  4. returnValue(boolean), IE Event对象没有preventDefault()函数,所以如果需要阻止浏览器的默认行为则需要通过设置returnValue的值,为false的时候表示阻止当前标签的浏览器默认行为。
  • IE事件句柄的注册
  1. IE事件的注册是通过使用attachEvent()方法和detachEvent()方法,与DOM Event中的addEventListener()和removeEventListener()类似,但是以下几点不同:
  1. 由于IE事件模型只支持事件的冒泡传播方式,因此attachEvent()方法和detachEvent()方法都只有两个参数,分别是事件类型和句柄函数。
  2. 传递给IE方法的事件类型与DOM Event中有所区别,DOM Event使用的时候不需要带前缀on,而IE Event则需要。比如说对于点击事件,在DOM Event事件中则是用click进行绑定,而在IE Event则需要使用onclick绑定。
  3. 用attachEvent()注册的函数将被作为全局函数调用,而不是作为发生事件的文档元素的方法。也就是说,在attachEvent()注册的时间句柄执行时,关键字this引用的是window对象,而不是事件的目标元素。
    4.attachEvent()允许同一事件句柄函数注册多次。当指定类型的一个事件发生的时候,注册函数被调用的次数和它被注册的次数一样多。

事件的兼容性写法

  • 获取event对象

    1
    var event = event || window.event;

    或者写成:

    1
    2
    3
    4
    var event;
    if(event){
    event = window.event;
    }
  • 获取事件发生的文档元素

    1
    var _srcElemtn = event.target || event.srcElement
  • 阻止当前事件冒泡

    1
    2
    3
    4
    5
    if(event.stopPropagation){
    event.stopPropagation();
    } else {
    event.cancelBubble = true;
    }
  • 阻止浏览器的默认行为

    1
    2
    3
    4
    5
    if(event.preventDefault){
    event.preventDefault();
    } else {
    event.returnValue = false;
    }

[4]阻止浏览器默认事件行为

  1. 首先我们来说说什么是浏览器的默认行为,比如对于一个html的a标签,浏览器的处理都是链接跳转。如果我们不想点击a标签的时候,浏览器进行页面跳转,这时候就需要阻止浏览器的跳转行为,而这个跳转行为就是浏览器默认的处理方式,也就是默认事件行为。
  2. 前面说到阻止浏览器的默认行为我们可以使用event.preventDefault()。但是很多时候,我们会看到代码中也有使用return false来阻止浏览器的默认行为。这小节主要就是来探讨使用event.preventDefault()和使用return false之间的区别。

    1
    2
    <a class='toggle'>Click Me!</a>
    <div id='mydiv'>MyDiv</div>
    1
    2
    3
    4
    $('a.toggle').click(function(){
    $('#mydiv').toggle();
    return false;
    });

    案例查看

    1. 通过上面的例子,我们可以看到在点击a标签的时候并没有发生浏览器页面跳转行为。当我们把JavaScript代码中的return false注释掉之后,再次点击a标签会发现浏览器进行了页面跳转。所以可以看到return false确实是可以用来阻止浏览器的默认行为。
    2. 我们将上面的return false换成event.preventDefault(),可以看到同样能阻止浏览器页面的跳转。案例查看
      3.既然return false 和 event.preventDefault() 都能阻止浏览器的默认行为,那是不是随便用哪个都OK呢?显然不是的,下面就来说说两者之间的差别。

Return false VS. event.preventDefault()

  1. 使用return false来取消浏览器默认行为的时候,其背后做了两件事情。分别是:

event.preventDefault()
event.stopPropagation()
什么鬼!?不要惊讶,就是酱紫。return false不禁阻止了浏览器的默认行为,而且还阻止了事件冒泡。具体Show by code!

1
2
3
4
5
6
7
8
9
10
11
12
<div class="post">
<h2><a href="http://www.scholat.com/zengweiquan">My Page</a></h2>
<div class="content">
Teaser text...
</div>
</div>
<div class="post">
<h2><a href="https://github.com/ScholatLouis">My Other Page</a></h2>
<div class="content">
Teaser text...
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$(function(){
$("div.post h2 a").click(function (event) {
var event = event || window.event;
var a = $(this),
href = a.attr('href'),
content = a.parent().next();
content.html(href + " #content");
return false;
});

var posts = $("div.post");
posts.click(function () {
alert("posts click!");
});
});

从代码中可以看出,使用return false之后,点击a标签并不会有弹框:posts click!出现。如果我们把return false换成event.preventDefault()之后,就可以看到posts click!的出现。案例查看
原因分析:

  1. 首先posts click!弹框是绑定在a标签的父元素div上。正常情况下,当a标签发生点击事件的时候,事件会向上冒泡,div绑定的click事件监测到点击事件的发生,则执行代码:alert(“posts click!”),于是出现了弹框。
  2. 使用return false之后,因为其背后执行了event.preventDefault(); event.stopPrapagation();这句代码,第一句首先是阻止了页面的跳转,所以我们可以看到点击a标签之后并没有发生页面跳转;第二句则是阻止点击事件向上冒泡,从而使得a标签的父元素div没能检测到click事件的发生,因此没有弹框posts click!的发生。

结论:

  1. 如果我们只是单纯需要阻止浏览器的默认行为,则应该使用event.preventDefault()而不要使用return false。

stopProgapation() VS. stopImmediatePropagation()

  1. 在阻止事件冒泡JavaScript也提供了两个事件函数,分别是stopProgapation()和stopImmediatePropagation()。这一部分,我们来说下这两个时间函数的区别。

stopProgapation()函数:

  1. 阻止当前事件向上传播,但是不影响当前元素所绑定的其他事件的执行。
    stopImmediatePropagation()函数:
  2. 阻止当前事件向上传播,同时当前元素绑定的相同事件都将不再执行,不会影响不同事件的执行。案例查看
1
2
3
4
5
6
7
8
9
10
11
12
<div class="post">
<h2><a href="http://www.scholat.com/zengweiquan">My Page</a></h2>
<div class="content">
Teaser text...
</div>
</div>
<div class="post">
<h2><a href="https://github.com/ScholatLouis">My Other Page</a></h2>
<div class="content">
Teaser text...
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$(function(){
$("div.post h2 a").click(function (event) {
var event = event || window.event;
var a = $(this),
href = a.attr('href'),
content = a.parent().next();
content.html(href + " #content");
event.preventDefault();
event.stopPropagation();
});
$("div.post h2 a").click(function(event){
var event = event || window.event;
alert("stopPropagation()");
});
var posts = $("div.post");
posts.click(function () {
alert("posts click!");
});
});
  1. 从上面的代码可以看到a标签绑定了两个click事件,第二个是一个弹框,内容为:stopPropagation() 。执行代码可以看到确实是会有弹框,且弹框内容为:stopPropagation()。案例查看
  2. 我们将上面的代码做出如下修改,将event.stopPropagation()改成event.stopImmediatePropagation()。案例查看。修改之后的代码执行,不再有弹框出现。