技术成长栈:

  1. Android
  2. Java、C/C++
  3. HTML + CSS + JavaScript
  4. Spring + Node.js + Vert.x
  5. 产品:Principle、xmind

这个章节,主要记录下我的JavaScript学习中碰到的问题

JavaScript中的原型继承模型

JavaScript中没有类似于Java、C++的类继承机制,JavaScript中的继承通过原型来实现。

这里的原型,我们可以类比于传统的父类,首先我们要明白:

  1. 所有的对象最终原型都是Object,Object定义了JavaScript世界的通用方法和成员,这点很类似与Java
  2. 当访问JavaScript对象的某个成员时(成员变量或者成员函数),会首先在本对象定义中查找该成员,如果找不到就从直接父原型(proto)中查找,如果还找不到,就因此向上查找,直到最顶端的Object原型,如果还没有找到就报错,这便是原型链查找。
  3. 每个函数都有一个prototype属性,这属性指向当前函数真正所属的对象(即定义它的对象)

明白了这三点,我们来看下继承的实现,实例代码如下:

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
//定义构造函数
function Person() {
}
//构造函数原型prototype属性定义及初始化
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
//为Person函数原型prototype定义函数sayName
Person.prototype.sayName = function () {
alert(this.name);
}
//调用Person构造函数,创建person1对象
var person1 = new Person();
//为实例person1增加name成员,可以通过 delete person1.name;语句来删除person1的name成员
person1.name = "Roket";
person1.sayName();
var person2 = new Person();
person2.__proto__.name = "Michle";
person2.sayName();
alert(person1.name === person2.name);
alert(person1.sayName === person2.sayName);

上述代码中,

  • 定义了Person构造函数,并给出了空实现
  • 定义Person的原型:在函数原型对象上定义name、age、job以及sayName成员
  • 声明var person1、person2两个Person对象,定义的时候调用了Person的空构造函数实例化,实例化完成之后,这两个Person对象,拥有同一个原型。
  • person1在本对象内,定义了name成员,并将”Rocket”赋值给了name成员。通过chrome F12 debug工具看到, person1具有name成员,值为”Rokcet”,此时其原型person1.prototype.name的值是”Nicholas”
  • person2通过person2.__proto__ 直接访问person2的原型对象,然后将name=”Michle”赋值给原型的name成员。 通过debug看到,person2对象本身没有name成员,其原型person2.prototype.name值被修改为”Michle”,而person1.prototype.name的值也被修改为了”Michle”值。
  • 根据原型链查找原理:person1.name=”Rockt”, person2.name=”Michle”,但是对于person1.sayName与person2.sayName,他们都是Person.prototype.sayName方法对象,是一个对象。

也就是说,JavaScript中,多个同原型实例会共享原型的所有成员,包括成员函数、成员变量,

因此,为了解决这个问题:

  1. 通常定义对象时都提供构造函数,用来初始化那些对象所私有的成员属性
  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
这是ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法。
## JavaScript中原型继承一些重要的概念
#### 1. 除了基本数据类型,所有的元素都是对象,函数Function、数组Array、对象Object
Function函数、Array函数、Object函数 以及 null对象,是由JavaScript虚拟机VM内部提供的对象,是一个全局唯一的对象。
#### 2. 每个对象都有一个 __proto__ 属性,指向的是创建该对象的那个函数对象(构造函数)的prototype属性
```javascript
//声明并定义函数对象Fn
function Fn() { }
Fn.prototype.name = 'name';
Fn.prototype.getYear = function () {
return 1998;
};
var fn = new Fn();
console.log(fn.name);
console.log(fn.getYear());
console.log(fn.__proto__ === Fn.prototype); //true

上面代码中:

  1. 对象fn由函数Fn()创建
  2. Fn函数的原型 Fn.prototype具有name属性、getYear()函数
  3. fn.__proto__ === Fn.prototype 为true

因此,证明本节的论点,也就是说,对象继承于构造函数的prototype,也就是说,对象的原型是构造函数的prototype

3. 每个函数(对象)都有一个__proto__属性,指向函数的构造函数Function对象的prototype属性:

因为,在JavaScript世界中,函数是一个Function对象,可以通过构造函数Function(…)来创建函数,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
var fn1 = function (x, y) {
return x + y;
};
var fn2 = new Function('x', 'y', "return x + y");
console.log(fn1(2, 2));
console.log(fn2(1, 2));
console.log(fn2.__proto__ === Function.prototype); //true
console.log(fn1.__proto__ === Function.prototype); //true
console.log(fn2.__proto__ === fn1.__proto__); //true

根据第二点,所以由Function函数创建的对象fn2的原型 fn2.__proto__ 指向的是构造函数 Function的prototype属性

4. 访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着proto这条链向上找,这就是原型链。

上图中,访问f1.b时,f1的基本属性中没有b,于是沿着__proto__找到了Foo.prototype.b。

判断对象原型链属性的位置的方法

  1. 使用 hasOwnProperty 来判断对象自身是否有定义属性
  2. 使用 in 操作符,来确定属性是否在原型链上存在

以下代码可以确定属性是存在与对象中,还是存在于原型中

1
2
3
function hasPrototypeProperty(object, popertyName){
return !object.hasOwnProperty(popertyName) && (popertyName in object);
}

5. instanceof 的原理

  1. instanceof 是一个二元运算符: 左侧 是一个普通对象,右侧是一个函数对象
  2. instanceof 运算过程:
    1. 左侧按照原型链查找,即依次追溯左侧对象的__proto__属性
    2. 右侧按照函数的prototype进行查找
  3. 两边查找到一个相同对象的引用,则运算结束,返回true,若找不到,则返回false

例如:

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
/**
* instanceof 运算符: 左边的是一个对象,右边的是一个函数
* 1. 左边从原型链往前查找
* 2. 右边从prototype查找
*/
/**
* Object是一个函数对象,它由Function构造函数创建,Object.__proto__ === Function.prototype
* Function是一个函数对象,直接找它的prototype,即Function.prototype
* 因此,该语句为true
*/
console.log(Object instanceof Function);
/**
* Function是一个函数对象,它由Function构造函数创建,因此Function.__proto_ == Function.prototype,
* 而Function.prototype是一个普通的Object对象,因此Function.prototype.__proto__ == Object.prototype
* Object是一个函数对象,直接找它的prototype,即Object.prototype
* 因此,语句为true
*/
console.log(Function instanceof Object);
/**
* Function是一个函数对象,它由Function构造函数创建,因此Function.__proto_ == Function.prototype
* Function是一个函数对象,直接找它的prototype,即Function.prototype
* 因此,语句为true
*/
console.log(Function instanceof Function);

6. this指针

来看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var obj = {
x: 10,
fn: function () {
console.log(this); //此时,this是obj这个对象
function f() {
console.log(this); //到这里,this指的是window
console.log(this.x);
}
f();
}
}
obj.fn();

这里 function f()在函数fn内部被定义,但是它是一个普通函数,它并不属于某个对象。因此它的this是window

再看另外一段代码

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* 函数栈帧的概念:
*
* 在执行一个函数的时候,JavaScript会为函数做一些准备工作,创建一个栈帧,然后:
* 1. 变量声明:
* 1)将函数块(域)中出现的变量,声明提升到最前面来,给默认undefined值
* 2)如果是自由变量,则往前面的栈帧查找该变量,如果最终到window栈帧都没找到,则在window栈帧默认为该变量声明,赋值undefined
* 3)如果是函数表达式,即var fun = function(...){...},则把fun当做普通变量处理,预定义为undefined。
* 2. this指针赋值,this可能是window、或者某个对象
* 3. 函数声明,即直接在函数块中声明:function fun(...){...},此时,直接声明fun函数,并给它赋值,这个值就指向定义的函数。
*
* @type {number}
*/
var x = 100;
var outer = {
x:10,
outFun:function () {
console.log(this); //对象outer
console.log(x); //this.x 与 x的区别
var myfun = function () {
console.log("outer.myfun()");
}
var inner = {
x: 20,
innerFun: function () {
console.log(this); //对象obj
console.log(x); //this.x 与 x的区别
//为this添加myfun函数,该函数的调用需要使用obj.myfun()的方式调用
this.myfun = function f() {
console.log(this); //对象obj
console.log(this.x); //对象obj.x
function f() {
console.log(this); //普通函数,window
console.log(this.x); //window没有x定义
}
f();
};
m = 1024;
//本栈中无法找到,则向前一个栈帧查找myfun()的定义
myfun();
var fun = this.myfun;
fun();
}
}
inner.innerFun();
}
}
outer.outFun();

在这里,function f()被赋值为this.now成员,在调用的时候,采用了this.now()来调用,因此,函数内部的直接this,就是这里的this,也就是obj这个对象。

闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var foo = function () {
var secret = 'secret';
return {
//内部对象:定义的函数
get_secret: function () {
return secret;
},
new_secret: function(newsecret){
secret = newsecret;
}
};
}(); //(),直接调用该函数
//JavaScript中,内部函数作用域,永远都可以访问上层作用域的变量
//从而导致了,本不能访问var secret变量的此处,可以通过get/new_secret函数来访问
//并且成员并不会立刻被销毁,只有闭包函数被销毁了,成员secret才会被销毁。
console.log(foo.get_secret());
console.log(foo.secret); //error
console.log(foo.new_secret('a new secret'));
console.log(foo.get_secret());

JavaScript 与 Java区别记录:

1.

2.