javascript 的prototype

JavaScript中的原型和对象机制


1 对象相关的一些语言特性

1.1 一切皆为对象
JavaScript里所有的东西都是对象. 对象是属性的集合. 数字, 字符串, 布尔值等原始值是"伪对象", 它们同样拥有属性, 但是是在栈上分配并按值传递. 而其他的对象是堆上分配并按引用传递.
一个很重要的概念是, 函数也是对象, 能够作为变量的值, 返回值, 参数或者属性的值. 函数对象特殊的地方是能通过"xxx()"语法执行包含在xxx函数对象内的代码. 因为这一特殊性, typeof xxx 将会返回function, 但这只是一种便利设施.

1.2 对象的属性可以动态添加和删除

复制代码
var  foo  =   new  Object();
//  为foo对象添加bar属性
foo.bar  =   " foobar " ;
alert(foo.bar); 
// foobar
//
 删除foo对象的bar属性
delete  foo.bar;
alert(foo.bar); 
// undefined
复制代码


1.3 除了宿主对象, 其它对象皆由构造函数创建
要有对象, 就先要有创建对象的方法. 
在C++/Java等语言, 这个方法就是实例化XXX类的一个实例xxx.
而在JavaScript的世界里实际没有类的东西, 当然仍然可以用"类"和"实例"等惯用语来描述JavaScript中类似的行为, 但其机制是完全不同的. JavaScript的对象是由构造函数创建的, 每个对象都有constructor属性表示创建该对象的构造函数:

复制代码
function  Test() {  this .a  =   " hello " ; }
var  test  =   new  Test();  //  由Test构造函数创建
alert(test.constructor);

var  o  =  { a:  " hello "  };
// 实际相当于
var  o_  =   new  Object();
o_.a 
=   " hello " //  由Object构造函数创建
alert(o.constructor);
复制代码

构造函数也是对象, 那构造函数是由什么创建? 内建的Function函数:

function  Test(a, b)
{
    alert(a+b)
;
}
//  相当于:
Test  =   new  Function([ " a " " b " ],  " alert(a+b); " );

Function函数又是由什么创建? 实际上Function是本机代码实现的固有对象. 不过为了一致性, Function也有constructor属性, 该属性指向它自己. 接上面的代码:

复制代码
/*  输出 function Function(){
            [native code]
        }
*/
alert(Test.constructor);

alert(Test.constructor.constructor 
===  Test.constructor);  //  true
alert(Test.constructor  ===  Object.constructor);  //  true
复制代码


2 原型prototype

2.1 prototype的概念
prototype是构造函数的一个属性, 该属性指向一个对象. 而这个对象将作为该构造函数所创建的所有实例的基引用(base reference), 可以把对象的基引用想像成一个自动创建的隐藏属性. 当访问对象的一个属性时, 首先查找对象本身, 找到则返回; 若不, 则查找基引用指向的对象的属性(如果还找不到实际上还会沿着原型链向上查找,  直至到根). 只要没有被覆盖的话, 对象原型的属性就能在所有的实例中找到.
原型默认为Object的新实例, 由于仍是对象, 故可以给该对象添加新的属性:

复制代码
//  prototype默认为new Object(); 为了方便, 记为p_obj
function  Person(name) {
    
this .name  =  name;
}

//  为 p_obj 增加 sayName 属性
Person.prototype.sayName  =   function (){
    alert(
this .name);
}

var  john  =   new  Person( " John " );  //  john 的 base reference指向p_obj
var  eric  =   new  Person( " Eric " );   //  eric 的 base reference也是指向p_obj

//  注意sayName代码中的this将指向实例化后的对象(this绑定)
john.sayName();  //  john对象本身没有sayName属性, 于是访问原型对象p_obj的sayName属性
eric.sayName();  //  访问同一个原型对象p_obj的sayName属性


var  tmp  =  Person.prototype;
tmp.boss 
=   " David " ;
//  于这个运行点, p_obj已经被修改
//
 根据上述属性访问流程, 新的修改(boss属性)能反映到所有的实例, 包括已经创建和即将创建的
alert( " John's boss is  "   +  john.boss);
alert(
" Eric's boss is  "   +  eric.boss);


//  hisCar和sayCar属性将增加到john对象而不是p_obj对象..
john.hisCar  =   " Audi " ;
john.sayCar 
=   function (){
    alert(
this .name  +   "  has a car of  "   +   this .hisCar);
}
john.sayCar();
//  ..因此下一句将错误, 因为eric对象本身和原型p_obj都没有sayName属性
/*
 eric.sayCar();  */
复制代码


2.2 原型链

除了能修改prototype指向的对象, 还能修改prototype指向哪一个对象, 即为prototype赋予一个不同的对象. 这可以实现一种简单的继承:

复制代码
function  Superman() {}
Superman.prototype.sayHello 
=   function (){
    alert(
" I'm a superman. " );
}

function  SupermanCan(skill){
    
this .skill  =  skill;
}
//  为prototype赋予Superman的实例..
SupermanCan.prototype  =   new  Superman();
//  ..再动态添加新的属性
SupermanCan.prototype.sayMore  =   function (){
    
this .sayHello(); // 调用"父类"的方法
    alert(
" I can  "   +   this .skill);
}

var  david  =   new  SupermanCan( " fly " );
//  output: I'm a superman. I can fly
david.sayMore();
复制代码

如果先实例化出一个对象, 再为构造函数prototype赋予一个不同的对象, 将会: 已经创建的对象的基引用不变, 将来创建的对象的基引用为新的原型对象:

复制代码
var  f1  =  {echo:  function () { alert( " sound " ); } };
function  Foo() {};
var  foo  =   new  Foo();  //  foo的基引用指向Object实例
Foo.prototype  =  f1;
/*  未定义, 因为这是"foo对象自己或者基引用指向的对象有echo属性吗?"
   而不是"foo对象自己或者Foo.prototype指向的对象有echo属性吗?" 
*/
alert(foo.echo);

var  foo2  =   new  Foo();  //  foo2的基引用指f1对象
foo2.echo();  //  output: sound
复制代码

所有的构造函数的prototype都不能为空, 就是说Superman.prototype = null 会被解释引擎无视;  另一方面, Object构造函数也有prototype属性(该属性是只读的, 可以为原型增加属性,但不能赋予不同的对象), 故因此可以有多层的原型链, 但原型链的根必定会是Object.prototype . 这意味着给Object.prototype增加属性影响到所有对象:

复制代码
Object.prototype.echo  =   function () {
    alert(
" hello " );
}

//  echo属性将增加到所有对象固有对象和自定义对象

var  arr  =   new  Array();
arr.echo();
Array.echo();

function  ObjCons()    {
    
this .dummy  =   " d " ;
}
var  obj  =   new  ObjCons();
obj.echo();
ObjCons.echo();
复制代码


3. 构造函数和new的实质

构造函数是一个地地道道的函数, 一个函数之所以能成为构造函数, 是因为new运算符:

复制代码
this .msg  =   " window " ;

function  Test()
{
    alert(
this .msg);
}

Test(); 
//  window
var  test  =   new  Test();  //  undefined. 因为test对象没有定义msg属性
复制代码

二者区别在于如何切入对象: Test() 在某个对象(例子中为window)的上下文上执行代码, 即this指向这个对象; new Test()创建一个新对象, 并以这个新的对象为上下文(this指向新对象)执行代码, 然后返回这个新对象. 
假如有个函数:

function  Test() {
    
var  dummy  =   " have money " ;
    
this .wish  =  dummy;
    doSomeThing();
    
}

结合以上的所有论述, 可以推测new Test()行为的伪代码表示为:
      创建一个新对象temp;
      temp.constructor = Test;
      temp.(base reference) = Test.prototype; // 这一句先于代码体执行, 意味着构造函数里的this.xxx能访问原型对象的属性xxx
      bind: this = temp; // 将this绑定到temp对象
      // 开始执行函数代码
      var dummy = "have money";
      this.wish = dummy; // 为temp对象添加wish属性
      doSomeThing();
      ....
      // 结束执行函数代码
      return temp;
这个未必会符合内部的二进制实现, 但却能很好地解释了JavaScript的特性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值