《JavaScript启示录》读后笔记

1、原型编程

每一个函数都有一个prototype属性,这个属性是指向一个对象的引用,这个对象称为原型对象,原型对象包含函数实例共享的方法和属性,也就是说将函数看成构造函数来调用的时候(使用new操做符调用),新建立的对象会从原型对象上继承属性和方法。浏览器

不管何时,只要建立了一个新函数,就会根据一组特定的规则为该函数建立一个prototype属性,默认状况下prototype属性会默认得到一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。编程语言

var helloworld = new Function('return "Hello World";');
console.log(helloworld());//输出Hello World
console.log(helloworld.prototype);//Object{}
console.log(helloworld.prototype.constructor);//输出function anonymous() {return "Hello World";}

2、构造函数实例都拥有指向构造函数的constructor属性
函数

默认状况下prototype属性都会得到一个constructor属性,所以任何对象实例化时,在幕后都会为实例添加constructor属性,而且这个属性指向建立对象的构造函数。下面建立一个Object对象,保存在变量foo中,而后验证constructor属性在建立对象后是否可用。性能

var foo = {};
console.log(foo.hasOwnProperty('constructor'));//输出false
console.log(foo.constructor === Object); //输出true,由于object()构建了foo
console.log(foo.constructor); //输出Object构造函数:function Object() { [native code] }

特别的,在原始值上使用constructor属性可以指向正确的构造函数。举一个例子:this

var foo = 23;
console.log(foo.constructor === Number); // 输出true

constructor属性也适用于用户自定义的构造函数。以下代码中,咱们定义了一个Person()构造函数,而后使用new关键字调用构造函数来生成一个对象,一旦建立了对象,就可使用constructor属性了。
spa

var Person = function Person() { return 'Wow!'; };
var person = new Person();
console.log(person.constructor === Person); // 输出true
console.log(person.constructor);//输出function Person() { return 'Wow!'; }

3、原型在全部function()实例上都是标准的prototype

即便不直接使用Function()构造函数(如var add = new Function('x', 'y', 'return x + y;');),而是使用字面量表示法(如var add = function(x, y) { return x + y; }),结果也都是同样,它老是拥有一个prototype属性,默认的prototype属性是一个空对象。设计

var helloworld = function () { return 'Hello World'; };
console.log(helloworld.prototype);//输出Object{},若是给function命名为Foo,则这里显示为Foo {}

事情上,上面这段代码至关于以下的代码:指针

var helloworld = function() { return 'Hello World'; };
foo.prototype = {};
console.log(helloworld.prototype);//输出Object{}

4、将构造函数建立的实例连接至构造函数的prototype属性

当调用构造函数建立一个实例的时候,实例内部将包含一个内部指针(不少浏览器这个指针名字为__proto__)指向构造函数的prototype,这个链接存在于实例和构造函数的prototype之间,而不是实例与构造函数之间。

Array.prototype.foo = 'foo';
var myArray = new Array();
console.log(myArray.__proto__.foo);//输出foo
//__proto__并非ECMA标准,更通用的方法以下
console.log(myArray.constructor.prototype.foo);//输出foo

5、原型链的最后是Object.prototype

每一个对象都有一个链接到原型的隐藏链接(__proto__),对象字面量产生的对象链接到Object.prototype,函数对象链接到Function.prototype(该原型对象自己链接到Object.prototype)。

原型链只有在检索值的时候才被用到,若是咱们尝试去获取对象的某个属性值,但该对象没有此属性,那么JavaScript会试着从原型对象中获取属性值。若是那个原型对象也没有该属性,那么再从它的原型中寻找,依此类推,直到该过程最后到达终点Object.prototype。若是这个属性彻底不存在于原型链中,那么结果就是undefined值。

6、用新对象替换prototype属性会删除默认构造函数属性

使用构造函数建立出来的实例,其constructor属性是从prototype里继承来的,但依然用instanceof判断类型。

var Foo = function Foo() {};
var Bar = function Bar() {};
Foo.prototype = { constructor: Bar };
var foo = new Foo();
console.log(foo.constructor);//输出function Bar() {} 
console.log(foo.constructor === Bar);//输出true
console.log(foo instanceof Foo);//输出true
console.log(foo instanceof Bar);//输出false

7、建立继承链

设计原型继承的目的是要在传统的面向对象编程语言中找到模仿继承模式的继承链,继承只是一个对象能够访问另一个对象的属性。

var Person = function Person() {
    //this.foo = 'thisFOO';
};
Person.prototype.foo = 'FOO';

var Teacher = function Teacher() {
};
Teacher.prototype.bar = 'BAR';

Teacher.prototype = new Person();

var teacher = new Teacher();

console.log(teacher.constructor);
//输出Person() {},由于这里teacher对象与new Person()对象都没有constructor,因此这里的constructor是Person原型的constructor。
console.log(teacher instanceof Teacher);
//输出true,虽然teacher.constructor为Person,但teacher为Teacher的实例。
/**
 * 一、teacher没有foo、bar属性,去teacher的原型里找,这里teacher的原型是Person的实例;
 * 二、在Person实例里依然找不到这2个属性;
 * (若是打开注释this.foo = 'thisFOO';,则foo属性至此已找到,输出thisFOO)
 * 三、由于在teacher的原型里没有找到foo、bar属性,则继续在Person实例的原型里找
 * (这里指的是Person.prototye),这一步找到了foo属性,输出FOO;
 * 四、而bar属性到最后的Object.prototype依然找不到,因此输出undefined
 */
console.log(teacher.foo, teacher.bar);