[JS] ECMAScript 6 - Prototype : compare with c#

 

开胃菜


 

prototype 对象

JavaScript 语言的继承则是通过“原型对象”(prototype)。

function Cat(name, color) {  // <----构造函数
  this.name  = name;
  this.color = color;
  this.meow  = function () {
    console.log('喵喵');
  };
}

var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');

cat1.meow === cat2.meow  // <----无法共享,这就是个问题,需要解决,否则浪费内存
// false

JavaScript 规定,每个函数都有一个prototype属性,指向一个对象,这个对象其实也有自己的原型对象,然后形成了一个原型链

 

共享属性

function Animal(name) {
  this.name = name;
}
Animal.prototype.color
= 'white';  // <---- 相当于一个共享属性的东西 var cat1 = new Animal('大毛'); var cat2 = new Animal('二毛'); cat1.color // 'white' cat2.color // 'white'

当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。

如果本来就有,当然是用自己本身的啦。

另一个例子:属性livesIn和price希望能够共享

你看到所有的鱼都有属性livesIn和price,我们甚至都没有为每一条不同的鱼特别声明这些属性。

这时因为当一个对象被创建时,这个构造函数 将会把 它的属性prototype ----> 新对象的内部属性__proto__

这个__proto__被这个对象用来查找它的属性

你也可以通过prototype来给所有对象添加共用的函数。这有一个好处:你不需要每次在构造一个对象的时候创建并初始化这个函数。

 

所有对象的祖宗 - Object.prototype

如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。

也就是说,所有对象都继承了Object.prototype的属性。

这就是所有对象都有valueOftoString方法的原因,因为这是从Object.prototype继承的。

因此,原型链的尽头就是null

Object.getPrototypeOf(Object.prototype)
// null

 

伪装之术

自己定义的对象MyArray, 其构造函数是MyArray,内部的prototype属性指向数组Array

var MyArray = function () {};  

MyArray.prototype = new Array();        // 指向一个数组实例
MyArray.prototype.constructor = MyArray;     // prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数

var mine = new MyArray();                   // 实例对象
mine.push(1, 2, 3);
mine.length // 3
mine instanceof Array // true

 

原型的构造函数就是自己

function P() {}
var p = new P();


/**
*
P类的prototype也即是上一级的constructor 就是它本身。
* 自己是没有constructor之说的,自己的其实就是自己原型的。

**/
p.constructor === P // true
p.constructor === P.prototype.constructor // true

p.hasOwnProperty('constructor') // false, 小p自己是没有constructor的

 

创建一个兄弟

function Constr() {}
var x = new Constr();

var y = new x.constructor();  // 调用自身的构造函数常见一个兄弟
y instanceof Constr        // true

雷区:修改原型对象时,一般要同时修改constructor属性的指向。

function Person(name) {
  this.name = name;
}

Person.prototype.constructor === Person // true


/**
* 构造函数Person的原型对象改掉了,但是没有修改constructor属性
* 那么,prototype.constructor当然就不是从前了
**/
Person.prototype = { method: function () {} }; Person.prototype.constructor === Person // false Person.prototype.constructor === Object // true 

雷图:修改原型对象时,一般要同时修改constructor属性的指向。

// 坏的写法
C.prototype = {
  method1: function (...) { ... },
  // ...
};
--------------------------------------------------------
// 好的写法 C.prototype = { constructor: C,  // <---- 记得一定要同时set construtor method1: function (...) { ... }, // ... }; // 更好的写法 C.prototype.method1 = function (...) { ... };

 

  

instanceof 运算符

是否在左边对象的原型链上 - 判断类型

var v = new Vehicle();
v instanceof Vehicle // true
v instanceof Object // true
Vehicle.prototype.isPrototypeOf(v)

// Jeff: instanceof运算符只能用于对象,不适用原始类型的值(such as string)。
var s = 'hello';
s instanceof String // false 

// 此外,对于undefinednullinstanceOf运算符总是返回false
undefined instanceof Object          // false
null      instanceof Object          // false

 

 

 

常用方法


 

People是对象的构造函数。

function People(name)
{
  this.name=name;
  //对象方法
  this.Introduce=function(){                     // (1) 生成一个实例,就能使用该方法
    alert("My name is "+this.name);
  }
}
-----------------------------------------------
//类方法 People.Run=function(){ // (2) 不需要通过生成实例,可直接使用的方法 alert("I can run"); }
-----------------------------------------------
//原型方法 People.prototype.IntroduceChinese=function(){ // (3) 给(1)带来了额外的补充 alert("我的名字是"+this.name); } -----------------------------------------------
//测试 var p1=new People("Windking"); p1.Introduce(); // (1) People.Run(); // (2) p1.IntroduceChinese(); // (3)

基本概念理解:

(1)、对象方法理解就很简单了,主要是如果类生成一个实例,那么该实例就能使用该方法
(2)、类方法,不需要通过生成实例就可以使用的方法
(3)、原型方法主要是用来对JS已有的系统对象进行扩展而生的,例如Array数组没有什么方法,你可以为其增加原型方法,那么创建的数组就拥有了该方法。

 

1、对象方法包括构造函数中的方法以及构造函数原型上面的方法;
2、类方法,其实这里的类就是一个函数,在js中由于函数也是一个对象,所以可以为函数添加属性以及方法,这种方法在node中用的比较多;
3、原型方法一般用于对象实例共享,比如Person.prototype.sayName=function(){console.log(this.name);};在原型上面添加该方法,就能实现共享。这样就不用每一次初始化一个实例的时候,为其分配相应的内存了。

 

添加共有方法

为什么要这么搞?因为我们可以先定义属性,然后在我们“想好了"之后,再添加方法。

// 1. 先定义好了必要的属性
function
Employee(name, salary){ this.name=name; this.salary=salary; }

// 2. 等知道如何操作了才定义方法,如此,灵活的不要不要的 Employee.prototype.getSalary
=function getSalaryFunction(){ return this.salary; } Employee.prototype.addSalary=function addSalaryFunction(addition){ this.salary=this.salary+addition; } var boss1=new Employee("Joan", 200000); var boss2=new Employee("Kim", 100000); var boss3=new Employee("Sam", 150000);

 

添加私有方法

function Aclass(){
  this.Property = 1;
  this.Method = function(){
      alert(1);
  }
}
var obj = new Aclass(); obj.Property2 = 2;        // 对象为自己添加了私有属性 obj.Method2 = function(){ // 对象为自己添加了私有方法
alert(
2); }
alert(obj.Property2); obj.Method2();

  

重写父类属性和方法

function AClass(){
    this.Property = 1;
    this.Method = function(){
        alert(1);
    }
}
function AClass2(){ this.Property2 = 2; this.Method2 = function(){ alert(2); } }
AClass2.prototype
= new AClass();    // AClass2继承了AClass?No! ----> 补充
AClass2.prototype.Property
= 3; // 然后重写了属性 AClass2.prototype.Method = function(){ // 之后重写了方法 alert(4); }
var obj = new AClass2(); alert(obj.Property); obj.Method();

 

补充 ---->

理解prototype不应把它和继承混淆。

可以出现这种情况:A的prototype是B的实例,同时B的prototype也是A的实例。

A的prototype为B的一个实例,可以理解A将B中的方法和属性全部克隆了一遍

例子:

定义了baseClass类,然后我们要定义extendClass

但是我们打算以baseClass的一个实例为原型,来克隆的extendClass,也同时包含showMsg这个对象方法。

function baseClass(){
   this.showMsg = function(){
       alert("baseClass::showMsg");   
   }
}
function extendClass(){}
extendClass.prototype
= new baseClass();  // 以一个实例(对象)为原型,全部克隆了一遍

// 扩展好了extendClass,再new个对象执行里面的方法
var instance = new extendClass(); instance.showMsg(); // 显示baseClass::showMsg

 

调用父类属性和方法 

如果我想使用extendClass的一个实例instance调用baseClass的对象方法showMsg怎么办?

function baseClass(){
    this.showMsg = function(){
        alert("baseClass::showMsg");   
    }
}

function extendClass(){
    this.showMsg =function (){      //有自己的,就不甩父类的同命名的方法了
        alert("extendClass::showMsg");
    }
}
----------------------------------------------------------------------------- extendClass.prototype
= new baseClass();
var instance = new extendClass();
instance.showMsg();
//显示extendClass::showMsg

 

但是,我还是偏偏想调用父类中的被覆盖的方法,怎么办? 

-----------------------------------------------------------------------------
extendClass.prototype = new baseClass();
var instance = new extendClass(); var baseinstance = new baseClass();  // 通过对象方法,而不是类方法!
baseinstance.showMsg.call(instance);
//显示baseClass::showMsg  <---- 使用call重定义this这个对象的

 

function定义的方法(对象方法 or 构造方法)有一个prototype属性:它是对象方法或者构造方法的专有属性。它指向一个prototype对象。

使用new生成的对象就没有这个prototype属性。

 

function Person(name)  
{  
   this.name=name;  
   this.showMe=function()  
        {  
           alert(this.name);  
        }  
};    
var one = new Person('js');
alert(one.prototype)
//undefined       // 使用new生成的对象就没有这个prototype属性。 alert(typeof Person.prototype); //object 但是这个就有 alert(Person.prototype.constructor); //function Person(name) {...};   prototype是Person的属性,该属性指向同名的prototype对象

 

 

 

对象中的Prototype相关


 

 

  • __proto__属性

读取或设置当前对象的prototype对象。

 

  • Object.setPrototypeOf()

ES6 正式推荐的设置原型对象的方法。

// 格式
Object.setPrototypeOf(object, prototype)

// 用法
const o = Object.setPrototypeOf({}, null);


/** 该方法等同于下面的函数 **/
function (obj, proto) {   obj.__proto__ = proto;   return obj; }

实际用法,设置共享属性

let proto = {};
let obj   = { x: 10 };
Object.setPrototypeOf(obj, proto);  // proto与obj有了关系,proto有了什么,obj就能有什么,有福共享的意思

// 如果第一个参数obj不是对象,会自动转为对象。
// 但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。
// 由于undefinednull无法转为对象,所以如果第一个参数是undefinednull,就会报错。

proto.y
= 20; proto.z = 40; obj.x // 10 obj.y // 20 obj.z // 40

 

  • Object.getPrototypeOf()

用于读取一个对象的原型对象。

 

  • super 关键字

ES6 新增,指向当前对象的原型对象

const proto = {
  foo: 'hello'
};

const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};

Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

 

雷区:表示原型对象时,只能用在对象的方法之中

第一种写法是super用在属性里面,

第二种和第三种写法是super用在一个函数里面,然后赋值给foo属性。

只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。

 

下面代码中,super.foo指向原型对象protofoo方法,但是绑定的this却还是当前对象obj,因此输出的就是world

const proto = {
  x: 'hello',
  foo() {            // <-- 指向了这里,但this却还是当前对象obj
    console.log(this.x);
  },
};

const obj = {
  x: 'world',
  foo() {
    super.foo();    // --> super.foo指向原型对象protofoo方法
  }
}

Object.setPrototypeOf(obj, proto);

obj.foo() // "world"

 

 

/* implemnet */

Javascript中call的使用

 

posted @ 2018-04-14 19:59  郝壹贰叁  阅读(228)  评论(0编辑  收藏  举报