JS 中原型和原型链深入理解
2018/05/05 · JavaScript · 原型
原文出处: erdu
首先要搞明白几个概念:
- 函数(function)
- 函数对象(function object)
- 本地对象(native object)
- 内置对象(build-in object)
- 宿主对象(host object)
大家好,我是IT修真院成都分院第7期的学员韩建名,一枚正直纯洁善良的WEB前端程序员。
函数
function foo(){ } var foo = function(){ }
1
2
3
4
5
6
|
function foo(){
}
var foo = function(){
}
|
前者为函数声明,后者为函数表达式。typeof foo
的结果都是function。
今天给大家带来的是:JS原型链
函数对象
函数就是对象,代表函数的对象就是函数对象
官方定义, 在Javascript中,每一个函数实际上都是一个函数对象.JavaScript代码中定义函数,或者调用Function创建函数时,最终都会以类似这样的形式调用Function函数:var newFun = new Function(funArgs, funBody)
其实也就是说,我们定义的函数,语法上,都称为函数对象,看我们如何去使用。如果我们单纯的把它当成一个函数使用,那么它就是函数,如果我们通过他来实例化出对象来使用,那么它就可以当成一个函数对象来使用,在面向对象的范畴里面,函数对象类似于类的概念。
var foo = new function(){ } typeof foo // object 或者 function Foo (){ } var foo = new Foo(); typeof foo // object
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var foo = new function(){
}
typeof foo // object
或者
function Foo (){
}
var foo = new Foo();
typeof foo // object
|
上面,我们所建立的对象
目录
本地对象
ECMA-262 把本地对象(native object)定义为“独立于宿主环境的 ECMAScript 实现提供的对象”。简单来说,本地对象就是 ECMA-262 定义的类(引用类型)。它们包括:
Object,Function,Array,String,Boolean,Number
Date,RegExp,Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError.
我们不能被他们起的名字是本地对象,就把他们理解成对象(虽然是事实上,它就是一个对象,因为JS中万物皆为对象),通过
typeof(Object) typeof(Array) typeof(Date) typeof(RegExp) typeof(Math)
1
2
3
4
5
6
|
typeof(Object)
typeof(Array)
typeof(Date)
typeof(RegExp)
typeof(Math)
|
返回的结果都是function
也就是说其实这些本地对象(类)是通过function建立起来的,
function Object(){ } function Array(){ } ...
1
2
3
4
5
6
7
|
function Object(){
}
function Array(){
}
...
|
可以看出Object原本就是一个函数,通过new Object()之后实例化后,创建对象。类似于JAVA中的类。
1.背景介绍
内置对象
ECMA-262 把内置对象(built-in object)定义为“由 ECMAScript 实现提供的、独立于宿主环境的所有对象,在 ECMAScript 程序开始执行时出现”。这意味着开发者不必明确实例化内置对象,它已被实例化了。ECMA-262 只定义了两个内置对象,即 Global 和 Math (它们也是本地对象,根据定义,每个内置对象都是本地对象)。
理清楚了这几个概念,有助于理解我们下面要讲述的原型和原型链。
2.知识剖析
prototype
prototype属性是每一个函数都具有的属性,但是不是一个对象都具有的属性。比如
function Foo(){ } var foo = new Foo();
1
2
3
4
5
|
function Foo(){
}
var foo = new Foo();
|
其中Foo中有prototype属性,而foo没有。但是foo中的隐含的__proto__属性指向Foo.prototype。
foo.__proto__ === Foo.prototype
1
|
foo.__proto__ === Foo.prototype
|
为什么会存在prototype属性?
Javascript里面所有的数据类型都是对象,为了使JavaScript实现面向对象的思想,就必须要能够实现‘继承’使所有的对象连接起来。而如何实现继承呢?JavaScript采用了类似C++,java的方式,通过new的方式来实现实例。
举个例子,child1,child2都是Mother的孩子,且是双胞胎。(虽然不是很好,但是还是很能说明问题的)
function Mother(name){ this.name = name; this.father = 'baba'; } var child1 = new Mother('huahua'); var child2 = new Mother('huihui');
1
2
3
4
5
6
|
function Mother(name){
this.name = name;
this.father = 'baba';
}
var child1 = new Mother('huahua');
var child2 = new Mother('huihui');
|
如果有一天,发现孩子的父亲其实是Baba,那么就要对孩子每一个孩子的father属性。
child1.father ='Baba'; console.log(child2.father) // baba
1
2
|
child1.father ='Baba';
console.log(child2.father) // baba
|
也就是说修改了其中一个孩子的father属性不会影响到下一个,属性的值无法共享。
正是这个原因才提出来prototype属性,把需要共享的属性放到构造函数也就是父类的实例中去。
3.常见问题
__proto__
__proto__属性是每一个对象以及函数都隐含的一个属性。对于每一个含有__proto__属性,他所指向的是创建他的构造函数的prototype。原型链就是通过这个属性构件的。
想像一下,如果一个函数对象(也成为构造函数)a的prototype是另一个函数对象b构件出的实例,a的实例就可以通过__proto__与b的原型链起来。而b的原型其实就是Object的实例,所以a的实例对象,就可以通过原型链和object的prototype链接起来。
function a(){ } function b(){ } var b1 = new b(); a.prototype = b1; var a1 = new a(); console.log(a1.__proto__===b1);//true console.log(a1.__proto__.__proto__===b.prototype) //true console.log(a1.__proto__.__proto__.__proto__===Object.prototype) //true
1
2
3
4
5
6
7
8
9
10
11
12
|
function a(){
}
function b(){
}
var b1 = new b();
a.prototype = b1;
var a1 = new a();
console.log(a1.__proto__===b1);//true
console.log(a1.__proto__.__proto__===b.prototype) //true
console.log(a1.__proto__.__proto__.__proto__===Object.prototype) //true
|
如果要理清原型和原型链的关系,首先要明确一下几个概念:
1.JS中的所有东西都是对象,函数也是对象, 而且是一种特殊的对象
2.JS中所有的东西都由Object衍生而来, 即所有东西原型链的终点指向Object.prototype
3.JS对象都有一个隐藏的__proto__属性,他指向创建它的构造函数的原型,但是有一个例外,Object.prototype.__proto__指向的是null。
4.JS中构造函数和实例(对象)之间的微妙关系
构造函数通过定义prototype来约定其实例的规格, 再通过 new 来构造出实例,他们的作用就是生产对象.
function Foo(){ } var foo = new Foo(); foo其实是通过Foo.prototype来生成实例的。
1
2
3
4
5
6
|
function Foo(){
}
var foo = new Foo();
foo其实是通过Foo.prototype来生成实例的。
|
构造函数本身又是方法(Function)的实例, 因此也可以查到它的__proto__(原型链)
function Foo(){ } 等价于 var Foo= new Function();
1
2
3
4
5
|
function Foo(){
}
等价于
var Foo= new Function();
|
而Function实际上是
function Function(){ Native Code } 也就是等价于 var Function= new Function();
1
2
3
4
5
|
function Function(){
Native Code
}
也就是等价于
var Function= new Function();
|
所以说Function是通过自己创建出来的。正常情况下对象的__proto__是指向创建它的构造函数的prototype的.所以Function的__proto__指向的Function.prototype
Object 实际上也是通过Function创建出来的
typeof(Object)//function 所以, function Object(){ Native Code } 等价于 var Object = new Function();
1
2
3
4
5
6
7
|
typeof(Object)//function
所以,
function Object(){
Native Code
}
等价于
var Object = new Function();
|
那么Object的__proto__指向的是Function.prototype,也即是
Object.__proto__ === Function.prototype //true
1
|
Object.__proto__ === Function.prototype //true
|
下面再来看Function.prototype的__proto__指向哪里
因为JS中所有的东西都是对象,那么,Function.prototype 也是对象,既然是对象,那么Function.prototype肯定是通过Object创建出来的,所以,
Function.prototype.__proto__ === Object.prototype //true
1
|
Function.prototype.__proto__ === Object.prototype //true
|
综上所述,Function和Object的原型以及原型链的关系可以归纳为下图。
对于单个的对象实例,如果通过Object创建,
var obj = new Object();
1
|
var obj = new Object();
|
那么它的原型和原型链的关系如下图。
如果通过函数对象来创建,
function Foo(){ } var foo = new Foo();
1
2
3
4
|
function Foo(){
}
var foo = new Foo();
|
那么它的原型和原型链的关系如下图
那JavaScript的整体的原型和原型链中的关系就很清晰了,如下图所示
1 赞 2 收藏 评论
4.解决方案
5.编码实战
6.扩展思考
7.参考文献
8.更多讨论
1.背景介绍
JavaScript对象是一个属性的集合,另外有一个隐式的对象:原型对象。原型的值可以是一个对象或者null。一般的引擎实现中,JS对象会包含若干个隐藏属性,对象的原型由这些隐藏属性之一引用,我们在本文中讨论时,将假定这个属性的名称为"__proto__"(事实上,SpiderMonkey内部正是使用了这个名称,但是规范中并未做要求,因此这个名称依赖于实现)。 由于原型对象本身也是对象,根据上边的定义,它也有自己的原型,而它自己的原型对象又可以有自己的原型,这样就组成了一条链,这个链就是原型链。
先来看看有什么用处
function Foo(){ this.y=2; } Foo.prototype.x=1; var obj3 = new Foo(); obj1.y;//2 obj1.x://1
这里就是用原型链继承了Foo的属性 obj1的原型(proto)会指向Foo的prototype属性 当函数声明的时候——function Foo(){} 实质上在做: 这个函数会有一个prototype属性,且默认会有两个属性 Foo.prototype { constructor:Foo, __proto__:Object.prototype } prototype是函数对象上的预设的对象属性,而原型通常都是其构造器的prototype属性 实例的__proto__属性会指向构造函数的prototype属性
2.知识剖析
ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法。
JavaScritp引擎在访问对象的属性时,如果在对象本身中没有找到,则会去原型链中查找,如果找到,直接返回值,如果整个链都遍历且没有找到属性,则返回undefined.原型链一般实现为一个链表,这样就可以按照一定的顺序来查找。
原型的动态性:对原型对象所做的实时修改都能从实例上立即反应出来。(注意区别添加修改和重写了原型对象)
原型对象包含指向构造函数的指针。a实例包含指向a原型对象内部的指针,使得我们可以访问原型对象的属性。如果让b原型对象等于a对象的实例那我们就可以访问a对象的原型对象及其属性和方法了。同理a的原型对象也可以是通过该种方法从c继承过来...
1.概念 ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。在JavaScript中,用 __proto__ 属性来表示一个对象的原型链。当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止!
(1)原型:创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按字面意思来理解,那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。 (2)原型对象:无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。
普通对象和函数对象
JS中万物皆对象,但对象也是有区别的,分为普通对象和函数对象,Object、Function是JS自带的函数对象,上栗子
var o1 = {}; var o2 =new Object(); var o3 = new f1(); function f1(){}; var f2 = function(){}; var f3 = new Function('str','console.log(str)'); console.log(typeof Object); //function console.log(typeof Function); //function console.log(typeof f1); //function console.log(typeof f2); //function console.log(typeof f3); //function console.log(typeof o1); //object console.log(typeof o2); //object console.log(typeof o3); //object
3.常见问题
原型链怎样实现继承
4.解决方案
原型对象包含指向构造函数的指针,这使得子类可以访问到构造函数中的属性。实例包含指向原型对象的内部的指针。
ex:
a实例包含指向a原型对象内部的指针,使得我们可以访问原型对象的属性。如果让b原型对象等于a对象的实例那我们就可以访问a对象的原型对象及其属性和方法了。同理a的原型对象也可以是通过该种方法从c继承过来...
5.编码实战
先给大家引入一个例子:组合使用构造函数模式和原型模式创建自定义类型
function Person(name,age,job){
this.name = name;
本文由澳门在线威尼斯官方发布于威尼斯澳门在线,转载请注明出处:中原型和原型链深远领会
关键词: