技术成长栈:
- Android
- Java、C/C++
- HTML + CSS + JavaScript
- Spring + Node.js + Vert.x
- 产品:Principle、xmind
这个章节,主要记录下我的JavaScript学习中碰到的问题
JavaScript中的原型继承模型
JavaScript中没有类似于Java、C++的类继承机制,JavaScript中的继承通过原型来实现。
这里的原型,我们可以类比于传统的父类,首先我们要明白:
- 所有的对象最终原型都是Object,Object定义了JavaScript世界的通用方法和成员,这点很类似与Java
- 当访问JavaScript对象的某个成员时(成员变量或者成员函数),会首先在本对象定义中查找该成员,如果找不到就从直接父原型(proto)中查找,如果还找不到,就因此向上查找,直到最顶端的Object原型,如果还没有找到就报错,这便是原型链查找。
- 每个函数都有一个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() { } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function () { alert(this.name); } var person1 = new Person(); 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 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
|
上面代码中:
- 对象fn由函数Fn()创建
- Fn函数的原型 Fn.prototype具有name属性、getYear()函数
- 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); console.log(fn1.__proto__ === Function.prototype); console.log(fn2.__proto__ === fn1.__proto__);
|
根据第二点,所以由Function函数创建的对象fn2的原型 fn2.__proto__ 指向的是构造函数 Function的prototype属性
4. 访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着proto这条链向上找,这就是原型链。
上图中,访问f1.b时,f1的基本属性中没有b,于是沿着__proto__找到了Foo.prototype.b。
判断对象原型链属性的位置的方法:
- 使用 hasOwnProperty 来判断对象自身是否有定义属性
- 使用 in 操作符,来确定属性是否在原型链上存在
以下代码可以确定属性是存在与对象中,还是存在于原型中
1 2 3
| function hasPrototypeProperty(object, popertyName){ return !object.hasOwnProperty(popertyName) && (popertyName in object); }
|
5. instanceof 的原理
- instanceof 是一个二元运算符: 左侧 是一个普通对象,右侧是一个函数对象
- instanceof 运算过程:
- 左侧按照原型链查找,即依次追溯左侧对象的__proto__属性
- 右侧按照函数的prototype进行查找
- 两边查找到一个相同对象的引用,则运算结束,返回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
| console.log(Object instanceof Function); console.log(Function instanceof Object); 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); function f() { console.log(this); 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
| var x = 100; var outer = { x:10, outFun:function () { console.log(this); console.log(x); var myfun = function () { console.log("outer.myfun()"); } var inner = { x: 20, innerFun: function () { console.log(this); console.log(x); this.myfun = function f() { console.log(this); console.log(this.x); function f() { console.log(this); console.log(this.x); } f(); }; m = 1024; 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; } }; }(); console.log(foo.get_secret()); console.log(foo.secret); console.log(foo.new_secret('a new secret')); console.log(foo.get_secret());
|
JavaScript 与 Java区别记录:
1.
2.