重新认识面向对象
1、首先来看下javascript组成:ECMAScirpt(核心)+BOM+DOM
2、如何理解javascript的面向对象?
面向对象有三个基本特性:封装、继承、多态。其中,对象系统中的继承特性有三种实现方案:基于类的(class-based)、基于原型(prototype-based)的、基于元类(metaclass-based)的。Javascript 是面向对象语言,比 Java 还要彻底的面向对象。在 Javascript没有使用常见的类继承体系,而是使用“构造器”来实现对象的类型化。构造器就是函数,惯例是首字母大写函数名来代表这是一个构造器,用 new
+构造器名字
来创建实例对象,在构造器(及其原型属性对象)内部,this
指代实例对象。Javascript采用“原型继承”而不是“类型继承”,通过对象的原型链进行属性(方法)的查找。
3、从对象类型开始理解:
3.1、JavaScript 是一门彻底的面向对象的语言,因此有必要从面向对象的概念着手 , 探讨一下面向对象中的几个概念:
a、一切事物皆对象
b、对象具有封装和继承特性
c、对象与对象之间使用消息通信,各自存在信息如何隐藏 ,通过一种叫做 原型(prototype)的方式来实现面向对象编程的。
来看下对象类型系统
上图可以看出:javascript对象系统图解
a、javascript数据类型可分两大类:“值类型”和“引用类型” 。这两中类型可以使用typeof方法加以区分
b、引用类型 即整个对象类型系统。大致分为原生对象+宿主对象+引擎扩展对象。其中原生对象包括内置对象和arguments,
c、对象类型:全文下来讨论的都是这两类对象----普通实例对象(object)和函数对象(function) (区分这两类对象 即:typeof 返回object /function)
注:本文主要讨论两种引用类型创建以及它们之间关系:对象(object/function)的创建以及的联系。
3.2、其中,ECMAScirpt定义数据六种基本类型:undefined boolean string number object function(引用类型),
3.3、理解两种引用类型之间关系 最重要的两点是:函数就是对象;对象是通过函数(即构造函数)创建的。
4、如何创建对象
上面说到两种引用类型之间的关系时谈到,函数就是对象,对象又是通过构造函数创建的。怎么感觉那么绕呢,这个问题像是先有鸡还是先有蛋的问题。这种问题能解释清楚吗?当然,但得一步步来吧,javascript中一切皆是对象,那就先从对象是如何创建的说起吧
创建对象的方法有两种,一种是使用new 操作符后跟Object 类型的构造函数 ,另一种则是对象字面量方法(创建对象的一种快捷方式:简化包含大量属性的对象的创建过程,即语法糖)。
// 构造函数方法--创建 var obj = new Object();//< == > obj = {} obj.name = 'foo'; obj.method = function(){}; //对象字面量方法--创建 var obj = { name:'foo', method:function{console.log(this.name);} }
就这么简单,没那么简单!!实际这中间发生了什么?
对象是如何创建的, 即实例化一个对象的过程,都是通过 new 构造器来实现的。来看下下面这段代码
var name = 'tom'
var Person = function(name,age){ this.name = name; this.age = age; } Person.prototype.say = function(){
console.log(this.name);
} var p = new Person('jerry','10');
console.log(p);//
p.say();//jerry
console.log('1、全局window作用域下的变量name:'+name);//tom ---new Person 并没有修改全局变量。
var p1 = Person('carry','12'); //如果把构造函数当作普通函数调用情况下,this 指向当前运行环境window。
console.log(p1);//undefined
//p1.say();
console.log('2、全局window作用域下的变量name:'+name);//carry ---全局对象被修改
a、var p ={};
b、p.__proto__ = Person.prototype;//原型链
c、Person.call(p,arg1,arg2);//初始化p对象
d、返回p对象
这种情况下,如果把构造函数当作普通函数调用情况下,有什么不同呢?
a、this 指向window(即可能修改全局变量)
b、不创建对象也不返回对象。//返回默认undefined
本文接下来将重点讨论几种通过创建对象模式,这样我们就能够更深刻理解对象与函数之间的关系的,同时对比这几种模式的优缺点。
思考:基于Object构造函数,创建了一个对象,该对象包含两个属性,其中一个为方法。如果需要很多类似obj的实例,那就会有许多重复的代码。
因此创建对象引入新的模式,其中包括了几种经典的模式;工厂模式,构造函数模式,原型模式,混合构造函数/原型模式,动态原型模式,寄生构造函数模式等。
4.1 工厂模式
/*工厂模*/ function person(name,age){ var obj = new Object();//通过Object构造器创建实例对象。 obj.name = name; obj.age = age; obj.say = function(){ console.log(this.name); }; return obj; } var person1 = person('zhangsan','18'); var person2 = person('lisi','3');
console.log(instanceOf person1)//object
console.log(instanceof person2)//object
缺:a、每次调用person函数,都会通过该函数内部的对象obj创建新的对象,然后返回,除此之外,这个为了创建新对象而存在的内部对象obj没有其他的用途。
b、另外,无法判断工厂模式创建的对象的类型(即 无法通过instanceOf等判断区分,都为Object 对象的一个实例 )。
4.2、构造函数模式
var Car = function (model, year, miles) { this.model = model; this.year = year; this.miles = miles; this.run =function(){ console.log(this.miles); } }; var baoma = new Car("Tom", 2009, 20000); var benchi = new Car("Dudu", 2010, 5000); console.log(baoma.constructor == Car);//constructor位于构造函数原型中,并指向构造函数,结果为true console.log(baoma instanceof Car);//通过instanceof操作符,判断baoma是否为构造函数car的实例 console.log(baoma.run == benchi.run);//false
优: 对比工厂模式,可以发现,这里并不需要创建中间对象(obj),也没有返回值。另外,可以将构造函数的实例标识为一种特定的类型(instanceOf Car),这就解决了工厂模式创建对象识别的问题(通过检查实例的constructor属性,或利用instanceof操作符检查该实例是否通过某个构造函数创建)。
缺:不过通过构造函数创建仍然会有自己的问题,实际上,run方法在每个实例上都会被重新创建一次,需要注意的是,通过new 构造函数,实例化创建的方法且并不相等,即可以通过比较baoma.run == benchi.run 得到 false 就能判断这两个方法并不相等。
next: 因此,虽然我们可以考虑将方法移到构造器外部(变为全局函数)来解决这个问题。但是 在全局下创建的全局函数实际上只能被经由Person创建的实例调用,这就有点名不副实了,控制使用权达不到仅限于当前对象的效果。
4.3 原型模式
首先来理解下函数:ECMAScript 中,我们声明的每个函数都是Function构造器(后面统一:实际为构造函数)的一个实例,即可以理解为,我们声明(无论是全局还是局部)的每一个函数都是new Function()创建出来的一个函数对象。
这里稍微跑下题:函数对象的有以下两种属性:
内部属性(即可以理解为函数执行过程中能访问的局部变量) arguments this, arguments类数组包含属性(callee);
自身属性:length(函数希望接收的参数个数)和prototype ,javascript中的每一个函数(function)都包含一个指向prototype属性的指针。
(大部分浏览器中实例对象可以通过__proto__访问内部属性),prototype属性实际上也是一个对象,其中包含了由某种引用类型(Object/Function/自定义构造器)创建的所有实例共享的属性和方法。如toString() ,valueOf();
这里强调下:大部分对象是可以通过for in 来访问其属性的,不过,虽然prototype 是一个对象,但它是不可枚举的,即不可以通过for in 来遍历访问
此外还有两个非继承而来的方法:call()、apply():作用是在指定的作用域执行函数,即重新设置函数内部属性 上下文执行环境this,当然也可以使用ECMAScript 5提供的bind 方法重新设置函数执行的上下文环境。
好回归正题^^
4.3.1 定义prototype属性
function Person() {} Person.name ='tom'; Person.prototype.friends = ['jerry','miqi','carry']; Person.prototype.logName = function() { console.log(this.name); } } var person1 = new Person(); person1.logName();//'tom' for(i in person1) {console.log(i);}
以上代码做了这几件事情:
1.定义了一个构造器Person,Person函数自动获得一个prototype属性,该属性默认只包含一个指向Person的constructor属性(当然大部分浏览器还会增加一个__proto__属性)。
来看看浏览器中运行是什么情况,卤煮随手在控制台打印一些代码并截图。从下图可以看到函数的prototype属性上包含constructor、__proto__这两个属性:constructor展开又有这两个属性,__proto__展开有一堆其它属性+constructor:一层层指向,能够往下一直展开无穷尽也。头大没有,反正卤煮是晕了。后面再详细讨论这些属性与对象和函数直接的关系和由来。
2.通过Person.prototype添加三个属性,其中一个作为方法;
3.创建一个Person的实例,随后在实例上调用了logName()方法。!!!这里需要注意的是logName()方法的调用过程:
a.在person1实例上查找logName()方法,发现没有这个方法,于是追溯到person1的原型prototype
b.在person1的原型上查找logame()方法,有这个方法,于是调用该方法 。因此基于这样一个查找过程,我们也可以通过在实例上定义原型中的同名属性,来阻止该实例访问原型上的同名属性,需要注意的是,这样做并不会删除原型上的同名属性,仅仅是阻止实例访问。
c、接下来,利用for-in循环枚举出实例可以访问到的所有属性(不论该属性存在于实例或是原型中),注意:这与上面提到的prototype 不可遍历访问并不冲突。至于为什么卤煮也不清楚^^。
好了,原型模式创建对象过程大概理解了,
提问:那怎么区分判断某个属性到底存在于实例上,还是存在于原型中?
我们可以看到__proto__列举出了对象Object构造器的原型上继承过来的方法:hasOwnProperty(),isPrototypeOf(),toString(),valueOf()等
答1: 这里我们可以利用hasOwnProperty()方法只有当属性存在于实例中,才会返回true:
console.log(person1.hasOwnProperty('name'));//true
答2:另外判断属性是否存在与某个实例对象,也可以通过同时使用in操作符和hasOwnProperty()方法来判断某个属性存在于实例中还是存在于原型中:
console.log(('friends' in person1) && !person1.hasOwnProperty('friends'));
//注释:先判断person1是否可以访问到friends属性,如果可以,再判断这个属性是否存在于实例当中(注意前面的!),如果不存在于实例中,就说明这个属性存在于原型中。
注意:hasOwnProperty来自Object的原型,是javascript中唯一一个在处理属性时不查找原型链的方法。
4.3.2 字面量方式(语法糖)重写prototype属性
前面提到,原型属性prototype也是对象,所以我们可以用对象字面量表示法书写原型,之前为原型添加代码的写法可以修改为:
function Person(){//使用对象字面量 重写Person 的原型 prototype属性
}
Person.prototype = { name: 'tom', friends: ['jerry','miqi','carry'], logName: function() { console.log(this.name); } }
var person1 = new Person(); //person1.logName();//'tom'
//对象字面量重写原型之后
console.log(person1.constructor);//function Object(){[native code]}构造函数
console.log(person1.constructor == Object);//true
console.log(person1.constructor == Person);//false
console.log(person1 instanceof Person);//true
console.log(person1 instanceof Object);//true
由于对象字面量语法重写了整个prototype原型,原先通过创建构造函数(new Object())时默认取得的constructor属性会指向Object构造函数,
不过,instanceof操作符仍会返回希望的结果:console.log(person1 instanceof Person);//true
因此,必要时可以在原型中手动设置constructor的值来解决这个问题。
Person.prototype = { constructor: Person, ...... }
从上面代码也可以看出所有对象都是Object的一个实例
通过原型模式创建也同样存在一些问题:如果在创建对象实例之后修改原型对象prototype某个属性或者重写整个原型对象,那么通过原型模式创建出来的对象在访问原型上方法时会出现什么样的结果呢?
4.3.3 修改原型对象某个属性
结论是:对原型的修改会立即在所有对象实例中反映出来:
var person1 = new Person(); Person.prototype.name = 'tidy';//修改原型name属性 console.log(person1.name);//'tidy'
实例和原型之间的连接仅仅是一个指针,而不是一个原型的拷贝。所以实际上是在实例和原型上一次搜索过程,对原型对象的所做的任何修改都会在所有对象实例中反映出来,就算在创建实例之后修改原型,也是如此。
4.3.4 如果在创建对象实例之后重写原型对象,情况又会如何?
function Person() {}; var person1 = new Person1();//创建的实例引用的是最初的原型 //重写了原型 Person.prototype = { friends: ['小花','二狗子','二愣子'] } var person2 = new Person();//这个实例引用新的原型 console.log(person2.friends); console.log(person1.friends);
以上代码在执行到最后一行时会出现未定义错误,如果我们用for-in循环枚举person1中的可访问属性时,会发现,里头空无一物,但是person2却可以访问到原型上的friends属性。 !重写原型切断了现有原型与之前创建的所有对象实例的联系,之前创建的对象实例的原型还在,只不过是旧的。
//创建person1时,原型对象还未被重写,因此,原型对象中的constructor还是默认的Person() console.log(person1.constructor);//Person() //但是person2的constructor指向Object() console.log(person2.constructor);//Object()
需要注意的是,原型模式忽略了为构造函数传递参数的过程,所有的实例都取得相同的属性值。同时,原型模式还存在着一个很大的问题,就是原型对象中的引用类型值会被所有实例共享,对引用类型值的修改,也会反映到所有对象实例当中。
function Person() {}; Person.prototype = { friends:['小花','二狗子','二愣子'] }
var person1 = new Person();
var person2 = new Person(); person1.friends.push('傻妞'); console.log(person2.friends);//['小花','二狗子','二愣子','傻妞']
从上述代码可以看出:修改person1的引用类型值friends,意味着person2中的friends也会发生变化,实际上,原型中保存的friends实际上只是一个指向堆中friends值的指针(这个指针的长度是固定的,保存在栈中),实例通过原型访问引用类型值时,也是按指针访问,而不是访问各自实例上的副本(这样的副本并不存在)。
4.4.组合使用构造函数和原型模式创建对象
上面讨论了构造函数和原型模式创建对象优势和缺点,发现可以将构造函数和原型模式的优点结合起来,弥补各自的不足,利用构造函数传递初始化参数,在其中定义实例属性,利用原型定义公用方法和公共属性,该模式应用最为广泛。
function Person(name,age) { this.name = name; this.age = age; this.friends = ['ajiao','jianjian','pangzi']; } Person.prototype = constructor: Person, logName: function() { console.log(this.name); } } var person1 = new Person('evansdiy','22'); var person2 = new Person('amy','21'); person1.logName();//'evansdiy' person1.friends.push('haixao'); console.log(person2.friends.length);//3
4.5.原型动态模式
原型动态模式将需要的所有信息都封装到构造函数中,通过if语句判断原型中的某个属性是否存在,若不存在(在第一次调用这个构造函数的时候),执行if语句内部的原型初始化代码。
function Person(name,age) { this.name = name; this.age = age; if(typeof this.logName != 'function') { Person.prototype.logName = function() { console.log(this.name); }; Person.prototype.logAge = function() { console.log(this.age); }; }; } var person1 = new Person('evansdiy','22');//初次调用构造函数,此时修改了原型 var person2 = new Person('amy','21');//此时logName()方法已经存在,不会再修改原型
需要注意的是,该模式不能使用对象字面量语法书写原型对象(这样会重写原型对象)。若重写原型,那么通过构造函数创建的第一实例可以访问的原型对象不会包含if语句中的原型对象属性。
function Person(name,age) { this.name = name; this.age = age; if(typeof this.logName != 'function') { Person.prototype = { logName: function() { console.log(this.name); }, logAge: function() { console.log(this.Age); } } }; } var person1 = new Person('evansdiy','22'); var person2 = new Person('amy','21'); person2.logName();//'amy' person1.logName();//logName()方法不存在
需要说明的是,各模式都有自己的应用场景,无所谓优劣。
5、如何区分这些对象?
上面讨论 对象如何创建的问题,接下来讨论的是如何区分这些对象(不管是object,还是function),这些对象有什么本质性区别。(注:本文讨论仅限于原生对象)
思考一: 有几类对象?
思路:JavaScript规范里写着有三种对象: 原生对象, 内置对象(属于原生对象); 宿主对象(本文不具体讨论,这是运行环境(浏览器)有关)
上文提到:在 Javascript没有使用常见的类继承体系,而是使用“构造器”来实现对象的类型化。从对象类型系统可以看出,原生对象中包含13内置对象,9个构造器。
因此可以简单理解为:两类对象
1、函数function:普通函数/构造函数(构造器)——由Function构造器创建的实例函数对象。
2、普通实例对象object:---由自定义构造函数或内置构造器创建
思考二:有几类构造函数?
思路:根据对象分类都是由构造函数,大致可分为:自定义构造器,内置构造器(包括Function 和Object、)
思考三:这些对象都有什么属性,对象与构造器之间有什么联系,属性是如何访问/继承(原型链概念)过来的?
思路:先来理解三个概念:原型、内部初始化属性、构造属性
1、prototype,每一个函数(构造器)都有一个显式的prototype属性,它代表了对象的原型(Function.prototype是个例外,没有prototype属性)。
__ptoto__
属性(IE浏览器不支持)是实例对象指向原型对象的一个指针,它的作用就是指向构造函数的原型属性prototype,通过这两个属性,就可以访问原型里的属性和方法了。Javascript
中的对象实例本质上是由一系列的属性
组成的,在这些属性中,每个实例对象都有一个的内部隐藏属性[[prototype]](浏览器通过__proto__把这个属性暴露出来了),指向于它所对应构造器的的原型对象(chrome、firefox中名称为__proto__,并且可以被访问到)。原型链正是基于__proto__才得以形成。
3、constructor: 返回创建此对象的构造函数的引用,即指向对象的构造函数
思考四:__proto__ 是什么?
每个实例对象都会在其内部初始化一个属性[[prototype]],大部分浏览器器就是__proto__来实现,当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去__proto__里找这个属性,这个__proto__又会有自己的__proto__,于是就这样 一直找下去,也就是我们平时所说的原型链的概念。不过值得注意的是:按照标准,__proto__是不对外公开的,也就是说是个私有属性。不过,大部分浏览器都对外暴露了这个属性。
思考结果:
a、“一切(引用类型)皆为对象”
b、 “每个函数(function)都有一个prototype”
c、 “每个对象(object)都有一个__proto__”
5.1、函数/构造函数 —— 函数也是一个对象。
先区分下普通函数与构造函数:与普通函数相比,构造函数有以下特点(不成文默认使用规范)
a.用new关键字调用
b.函数内部可以使用this关键字
c.默认不用return返回值
d.函数命名建议首字母大写,与普通函数区分开。
当然,构造函数也可以当做普通函数来使用,不过内部使用this 就跟当前运行上下文绑定到一起了
5.1.1 所有自定义构造器/函数实际上也是一个对象,这里比较特殊的是它的__proto__都指向Function.prototype 。
JavaScript中有内置(build-in)构造器/对象共计13个(ES5中新加了JSON,不包括null),这里列举了可访问的9个构造器(可使用new 创建实例对象)。剩下如Global不能直接访问,Arguments仅在函数调用时由JS引擎创建,Math,JSON是以对象形式存在的,无需new。它们的__proto__是Object.prototype。如下
Number.__proto__ === Function.prototype //true Boolean.__proto__ === Function.prototype //true String.__proto__ === Function.prototype //true Object.__proto__ === Function.prototype//true Function.__proto__ === Function.prototype //true Array.__proto__ === Function.prototype // true RegExp.__proto__ === Function.prototype // true Error.__proto__ === Function.prototype // true Date.__proto__ === Function.prototype // true //普通函数对象+自定义构造函数 var func1 = function(){console.log()} function Person(name){ this.name = name; this.logName = function(){}; } console.log(func1.__proto__ === Function.prototype);//true console.log(func2.__proto__ === Function.prototype);//true //结论所有自定义构造器/函数和内置构造器实际上也是一个实例对象(构造函数Function 的实例对象) //它内部初始化属性__proto__都指向Function.prototype (function(){console.log(arguments.__proto__ == Object.prototype)})() //true Math.__proto__ === Object.prototype // true JSON.__proto__ === Object.prototype // true
从上面例子可以看出什么?所有的函数/构造函数(构造器)都来自于Function.prototype,甚至包括根构造器Object及Function自身(通俗的讲,函数/构造函数都是构造器Function的一个实例)。因此,所有构造器都继承了Function.prototype的属性及方法。如length、call、apply、bind(ES5)等属性或方法。
那我们可能会产生疑问,所有函数/构造器都来自于 Function.prototype ,prototype 本身也是一个不可枚举对象(所有对象都有一个隐藏内部属性,指向其对应构造函数的原型),它又是由那个构造器创建的?
console.log(Function.prototype.__proto__ === Object.prototype) // true
一目了然,原来是来自于Object.prototype, 好吧,追根朔源,Object.prototype的__proto__。
Object.prototype.__proto__ === null // true
好吧到顶了,没法再继续了》》
5.1.2 上面讨论了JavaScript引擎内置构造器和自定义构造的__proto__,所有对象的__proto__都指向其构造器的prototype;下面再来看下通过这些构造器创建实例对象的__proto__
var date = new Date(); var error = new Error(); var person = new Person(); console.log(date.__proto__ === Date.prototype);//true console.log(error.__proto__ === Error.prototype);//true console.log(person.__proto__ === Person.prototype);//true
5.2 所有对象(包括函数/构造函数(function)和普通实例对象(object))。
注意:每个实例对象都又有一个constructor属性,可以获取它的构造器。即我以看下下面这段代码
//函数对象/构造函数-- Number.constructor === Function // true Boolean.constructor === Function// true String.constructor === Function// true Object.constructor === Function// true Function.constructor === Function// true Array.constructor === Function// true RegExp.constructor === Function// true Error.constructor === Function// true Date.constructor === Function// true Function.constructor=== Function //true 即: Function.constructor === Function //实例对象 var num = new Number(2); var date = new Date(); var str = new String(); var obj = new Object(); var func1 = new Function(); var error = new Error(); var arr = new Array(); var rep = new RegExp(); var person = new Person(); num.constructor. __proto__ === Function.prototype // true date.constructor. __proto__ === Function.prototype // true str.constructor. __proto__ === Function.prototype // true obj.constructor. __proto__ === Function.prototype // true error.constructor. __proto__ === Function.prototype // true func1.constructor. __proto__ === Function.prototype // true arr.constructor. __proto__ === Function.prototype // true rep.constructor. __proto__ === Function.prototype // true person.constructor. __proto__ === Function.prototype;
从而可以得出结论:
person.__proto__ === Perosion.protoype === person.constructor.prototype 即 指向同一个对象
上面提到过:用过字面量方式重写原型对象prototype的值会修改prototype的执行为Object
function Person(name) { this.name = name } // 重写原型 Person.prototype = { getName: function() {} } var p = new Person('jack') console.log(p.__proto__ === Person.prototype) // true console.log(p.__proto__ === p.constructor.prototype) // false //等价于 对象直接量{getName: function(){}}与根构造器 Object.protype 的空对象 {}比较 console.log(Object === p.constructor.prototype) // true 是因为 重写原型之后修改了constructor属性 // 建议重写原型对象默认增加constructor属性,修正原型指向 Person.prototype = { constructor:Person, getName: function() {} }
注意:__proto__目前在IE6/7/8/9中都不支持。
6、内存图解prototype、__proto__、constructor与Object、Function
这样将帮助我们更加容易理解原型继承原理,及原型链查找属性方法的过程。不多说,上图上代码:
6.1 关于上面提到的函数对象,我们来看以下例子,来说明:
var o1 = {}; var o2 =new Object(); function f1(){} var f2 = function(){} var f3 = new Function('str','console.log(str)'); f3('aabb'); // aabb typeof Object //function typeof Function //function typeof o1 //object typeof o2 //object typeof f1//function typeof f2//function typeof f3 //function
- a、通常我们认为o1、o2是对象,即普通对象(由Object或自定义构造函数创建);f1、f2、f3为函数。
- b、但是其实函数也是对象,是由Function构造的,
- c、f3这种写法就跟对象的创建的写法一样。f1、f2最终也都像f3一样是有Function这个函数构造出来的
- d、f1、f2、f3为函数对象,Function跟Object本身也是函数对象。
function Animal(){ } var anim = new Animal(); console.log('***********Animal anim proto*****************'); typeof Animal.prototype //object anim.__proto__===Animal.prototype //true Animal.__proto__===Function.prototype)); //true Animal.prototype.__proto__===Object.prototype)); //true console.log('***********Function proto*****************'); typeof Function.prototype //function typeof Function.__proto__ //function typeof Function.prototype.prototype //undefined typeof Function.prototype.__proto__ //object Function.prototype===Function.__proto__ //true console.log('***********Object proto*****************'); typeof Object.prototype) //object typeof Object.__proto__ //function Object.prototype.prototype //undefied Object.prototype.__proto__===null; //null console.log('***********Function Object proto关系*****************'); Function.prototype===Object.__proto__; //true Function.__proto__===Object.__proto__; //true Function.prototype.__proto__===Object.prototype; //true
上面的内存图跟堆栈结构可以参照文章Javascript_01_理解内存分配。
堆区图说明:
Function.prototype函数对象图内部表示prototype属性的红色虚框,只是为了说明这个属性不存在。
- 所有对象所有对象,包括函数对象的原型链最终都指向了Object.prototype,而Object.prototype.__proto__===null,原型链至此结束。
- Animal.prototype是一个普通对象。
- Object是一个函数对象,也是Function构造的,Object.prototype是一个普通对象。
- Object.prototype.__type__指向null。
- Function.prototype是一个函数对象,前面说函数对象都有一个显示的prototype属性,但是Function.prototype却没有prototype属性,即Function.prototype.prototype===undefined,所有Function.prototype函数对象是一个特例,没有prototype属性。
- Object虽是Function构造的一个函数对象,但是Object.prototype没有指向Function.prototype,即Object.prototype!==Function.prototype。
6.2 Prototype跟Constructor关系介绍
console.log('**************constructor****************'); console.log('anim.constructor===Animal:'+(anim.constructor===Animal)) ; //true console.log('Animal===Animal.prototype.constructor:'+(Animal===Animal.prototype.constructor)) ; //true console.log('Animal.constructor===Function.prototype.constructor:'+(Animal.constructor===Function.prototype.constructor)); //true console.log('Function.prototype.constructor===Function:'+(Function.prototype.constructor===Function)); //true console.log('Function.constructor===Function.prototype.constructor:'+(Function.constructor===Function.prototype.constructor)); //true console.log('Object.prototype.constructor===Object:'+(Object.prototype.constructor===Object)); //true console.log('Object.constructor====Function:'+(Object.constructor===Function)); //true
prototype、constructor内存关系图(在Function、Object、Prototype关系图上加入constructor元素):
- 注意Object.constructor===Function;本身Object就是Function函数构造出来的
- 如何查找一个对象的constructor,就是在该对象的原型链上寻找碰到的第一个constructor属性所指向的对象。
7、函数/构造器从Function 继承什么属性, 普通对象从Object获取到了什么属性。
也许上面这些还不够味,对象创建都能构造器原型继承隐藏属性的,这些隐藏属性是什么?
下面通过两张图来看下具体从这两个引用类型获得什么属性:
7.1 这张图展示__proto__(object类型--构造器为Object)从Object构造器继承一系列属性方法
卤煮在控制台定义了一个普通函数,并打印出来这个函数原型prototype,发现prototype也是个对象,这个对象包含两个属性:浏览器对外暴露的对象内部私有化属性__proto__和构造器constructor;其中constructor是一个函数对象,__proto__是一个普通对象(构造器为Object,因此继承了Object的许多方法)。
7.2 这张图展示constructor (function类型--构造器为Function)从Function构造器继承一系列属性方法
从上图可以看出 constructor 是一个函数对象,实际上 (function(){}).constructor.__proto__ = Function.prototype / (function(){}).__proto__ = Function.prototype,即任何一个函数的__proto__都指向Function.prototype, 任何一个对象的constructor(实际也为函数)的__proto__都指向Function.prototype(图中也很直观显示constructor 的构造器为 Function)
为什么要说上面这一段呢?因为 在控制台 打印 console.log((function(){}).__proto__) 并不能直观看到Function的原型是什么--->所以只能间接通过constructor 查看Function.prototype属性:
apply:
call:
bind:
length:
name:
arguments:
总结:
1、全文下来:都是在说两个引用类型(javascript 一切皆是对象):函数(function)和对象(object) ——函数对象,普通实例对象。简单的说可以用typeof()来区分:function/object
2、所有对象都包含一个内部私有化属性___proto__ 和constructor
3、函数都包含一个prototype属性,同时作为一个对象,他也有__proto__ 和constructor属性
4、几个特殊的对象:
a、prototype:作为函数对象的属性,本质上也是一个实例对象,所以也有__proto__ 和constructor属性,特别的Function.prototype 是函数,但没有prototype属性;
b、__proto__ :作为所有对象都有的内部初始化属性(浏览器对外暴露的属性),指向对象的构造函数的原型prototype。
大致分两种情况:普通实例对象__proto__ 指向其构造器的prototype,函数对象__proto__ 均为:Function.prototype
c、constructor:作为所有对象一个属性,本质上是一个函数,指向对象的构造器(三种):object function 自定义构造器;特别的 prototype 作为一个对象,其constructor又反过来引用函数本身
即 func.prototype.constructor = func 因此可以理解为 原型对象prototype的 constructor是有对应函数构造的
5、作为普通实例对象从构造器Object获得什么属性或方法:
defineProperty:定义属性
hasProperty:判断是否拥有属性
isPrototypeOf():
propertyIsEnnumberable():
另外还有:toString(),toLocaleString(),valueOf()
6、作为函数从构造器Function获得哪些属性方法:
apply,call,bind,length, name,arguments,this。
7、通过 __proto__ 形成原型链。