[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
的属性。
这就是所有对象都有valueOf
和toString
方法的原因,因为这是从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
// 此外,对于undefined
和null
,instanceOf
运算符总是返回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这个对象的
-
郭培:Javascript中call的使用【关于以上的call的使用】
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不是对象,会自动转为对象。
// 但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。
// 由于undefined
和null
无法转为对象,所以如果第一个参数是undefined
或null
,就会报错。
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
指向原型对象proto
的foo
方法,但是绑定的this
却还是当前对象obj
,因此输出的就是world
。
const proto = { x: 'hello', foo() { // <-- 指向了这里,但this却还是当前对象obj
console.log(this.x); }, }; const obj = { x: 'world', foo() { super.foo(); // -->super.foo
指向原型对象proto
的foo
方法 } } Object.setPrototypeOf(obj, proto); obj.foo() // "world"
/* implemnet */
Javascript中call的使用