js 面向对象的设计(基于原型链)

对象。每个对象都有一个内部链接到另一个对象,称为它的原型 prototype。该原型对象有自己的原型,等等,直到达到一个以null为原型的对象。根据定义,null没有原型,并且作为这个原型链 prototype chain中的最终链接。

属性类型

  1. 数据属性
// 用于修改属性的描述符
var person = {};
Object.defineProperty(person,"name",{
  Configurable:false,// 允许通过delete删除属性,重新定义属性?默认true
  Enumerable:false,// 允许枚举?默认true
  writable:false,// 允许修改值?默认true
  Value:"nicholas",// 值默认 undefined
});
person.name;//nicholas
person.name = "greg";
person.name;//nicholas
var person = {};
Object.defineProperty(person,"name",{
  Configurable:false,
  Value:"nicholas",// 值默认 undefined
});
// 抛出错误
Object.defineProperty(person,"name",{
  Configurable:true,
  Value:"nicholas",// 值默认 undefined
});
  1. 访问器属性
Configurable:false,// 允许通过delete删除属性,重新定义属性?默认true
Enumerable:false,// 允许枚举?默认true
get,// 读 类型function 值默认 undefined 
set,// 写 类型function 值默认 undefined
var book = {
  _year:2004,
  edition:1
};
Object.defineProperty(book,"year",{
  get: function(){
    return this._year;
  },
  set: function(newValue){
    this._year =  newValue;
    this.edition += 1;
  }
});
book.year = 2005;
book.edition;//1
// 属性前的 _ 是一个记号,表示只能通过对象方法访问的属性
// 仅制定get,属性不可写,尝试写会被忽略
// defineProperty 出现之前,是这样写的如下:
var book = {
  _year:2004,
  edition:1
};
book.__defineGetter__("year",function(){
    return this._year;
});
book.__defineSetter__("year",function(newValue){
    this._year =  newValue;
    this.edition += 1;
  }
});
book.year = 2005;
book.edition;//1

定义多个属性

var book = {};
Object.defineProperty(book,{
  _year:{
    value:2004
},
  edition:{
    value:1
},
 year:{
    get: function(){
      return this._year;
    },
    set: function(newValue){
      this._year = newValue;
      this.edition+=1;
    }
}
});
//_year, edition 两个数据属性,数据属性
// year 一个访问属性(访问器),
// 读取属性的特性
var descriptor =  Object.getOwnPropertyDescriptor(book,"_year");
descriptor.value;// 2004
descriptor.configurable;// false
typeof descriptor.get;// undefined
// 访问属性的特性
var descriptor =  Object.getOwnPropertyDescriptor(book,"year");
descriptor.value;// undefined
descriptor.configurable;// false
typeof descriptor.get;// function

创建对象

  1. 工厂模式
function createdPerson(name,age){
  var person = new Object();
  person.name = name;
  persion.age = age;
  return person;
}
var person1 =  createdPerson("xiaoming",29);
var person2 =  createdPerson("xiaohong",29);
  1. 构造函数
// 上面代码改良如下
function Person(name, age){
  this.name = name;
  this.age = age;
}
var person1 = new Person("xiaoming",29);
var person2 = new Person("xiaohong",29);
// 通过构造函数创建的实例,constructor属性保存指向Person
person1. constructor;//Person
person1 instance Object;//true
person1 instance Person;//true
// 通上,所有对象继承自Object
// 构造函数的缺点
function Person(name, age){
  this.name = name;
  this.age = age;
  this.say = function (){
   alert(this.name);
}
}
var person1 = new Person("xiaoming",29);
var person2 = new Person("xiaohong",29);
// 这样person1和person2会存在,两个相同功能的function对象,造成资源的浪费
// 改进
function Person(name, age){
  this.name = name;
  this.age = age;
  this.say = say;
}
function say(){
  alert(this.name);
}

原型对象

// 上面的解决方案,导致新的问题:在全局作用域的函数缺只能被某个对象是用,且若很多方法的话
// 需定义很多的全局函数。则毫无封装。。
// 原型模式:(原理,基于函数的prototype属性,此属性指向函数的原型对象)
//上面代码改称为
function Persion(){
}
Persion.prototype.name = "xiaoming";
Persion.prototype.age =  29;
Persion.prototype.say = function (){
   alert(this.name);
};
var person1 = new Person();
person1.say();//xiaoming
// 这些属性是所有实例共享的

Person,Persion.prototype,person1,person2关系如下:

EB2704EC-9242-4027-B33F-2B289E45A266.png

对象,原型,实例关系的参考
注意:当我们创建一个函数,系统就会为这个函数自动分配一个prototype指针,指向它的原型对象。并且可以发现,这个原型对象包含两个部分(constructor 和 proto)其中constructor指向函数自身。(这里形成了一个小闭环)  当我们将该函数作为模版创建实例(new方法)的时候,我们发现创建出的实例是一个与构造函数同名的object,这个object是独立的,他只包含了一个proto指针(实例没有prototype,强行访问则会输出undefined),这个指针指向上面提到的构造函数的prototype原型对象。  这时候我们发现三者形成了一个大"闭环"。之所以加上引号,因为构造函数和实例之间无法直接访问,需要通过proto指针间接读取。
prototype:此属性只有函数有
constructor:函数的原型对象的属性,指向函数本身
prototype:函数的属性,指向函数的原型对象
proto:实例的属性,指向函数的原型对象,构造函数和实例之间无法直接访问。

person1.__proto__ == Person.prototype;// true
person1.__proto__.constructor == People;//true
// 查找对象是否存在原型关系
Person.prototype.isPrototypeof(person1);// true
Person.prototype.isPrototypeof(person2);// true
// 获取Prototype
Object.getPrototypeof(person1) == Person.prototype;//true
Object.getPrototypeof(person1).name;//xiaoming

这种情况下,person1.name;实际是查询的原型链上的name属性,若查找.一个不存在的属性,那么会遍历原型链所有的属性,很浪费性能

// 原型链
function Persion(){
}
Persion.prototype.name = "xiaoming";
Persion.prototype.age =  29;
Persion.prototype.say = function (){
   alert(this.name);
};
var person1 = new Person();
person1.say();//xiaoming  来自原型
var person2 = new Person();
person2.name = "xiaohong";
person2.say();//xiaohong 来自实例
// 原型链的查找,先查自身的属性,查不到依次往上查原型链,
// delete 操作符,删除实例的属性
delete  person2.name;
person2.say();//xiaoming  来自原型
// hasOwnProperty(); 检测一个属性存在实例还是原型,参数是属性名
// 原理见下图
EB411223-B4E6-4542-80F1-8C7BD0F8A32E.png
// in操作符:无论存在于原型还是实例,存在即返回true
var person1 = new Person();
var person2 = new Person();
person1.hasOwnProperty("name");//false
"name" in person1;//true

person1.name = "xiaoming";
person1.hasOwnProperty("name");//true
"name" in person1;//true
// 应用
// 判断是原型属性
function hasPrototypeProperty(object , name){
  return ! object.hasOwnProperty(name)&&(name in object);
}
//for in 可以获取对象所有可枚举的属性[enumerable],还可以使用Object.keys()方法
function Person(){
}
Person.prototype.name = "xiaoming";
Person.prototype.age = 29;
Person.prototype.say = function (){
 alert(this.name);
}
// 获取实例属性
var keys = Object.keys(Person.prototype);// name,age,say
var p1 = new Person();
p1.name = "11";
p1.age = 22;
var keys = Object.keys(p1);// name age
// 获取所有属性无论是否可以枚举
var keys = Object.getOwnPropertyNames(Person.prototype);// constructor,name,age,say
// 更简单的原型语法
// 此方式会导致enumerable属性被标记为true,原声的enumerable默认事false不可枚举
function Person(){
}
// 重写原型
Person.prototype = {
 constructor:Person,//将其设为Person保证通过该属性获取适当值
  name:"nike",
  age:29,
  say:function (){
 alert(this.name);
  }
};
// 结合defineproperty函数 
//最上面有defineProperty函数的讲解,第一个参数实例,第二个参数属性名,第三个参数设置
// 这是数据属性的设置
Object.defineProperty(Person.property,"constructor",{
  enumerable:false,
  value:Person
});
}
// 注意一下俩例子,如图,实例的原型,是一个对象,有自己的属性
43BF19B3-7244-4521-8CEC-BC5190A52C25.png

32514972-B563-4A0B-A29B-E1EE25704248.png
// 动态原型模式(懒加载)
function Person(name ,age){
  this.name = name;
  this.age = age;
  if(typed this.sayname != "function"){
      Person.prototype.sayname = function (){
          alert(this.name);
      }
  }
}
// 装饰模式
// 创建一个具有额外方法的特殊数组
function SpecialArray(){
  var values = new Array();
  // 数组的push属性(类型function),调用apply函数,添加值
  values.push.apple(values,arguments);//values :当前作用域 arguments :参数列表
  // 添加方法
  values.toPipedString = function(){
    return this.join("|");
  }
  return values;
}
var colors = new  SpecialArray("red","blue","green");
colors.toPipedString();//red|blue|green
// 稳妥构造,创建私有属性
function Person (name,age,job){
  var o = new Object();
  o.sayName = function(){
    alert(name);
  }
return o;
}
// 这种情况下,只有sayName才能访问name
var friend = new Person("xiaoming",29,"ht");
friend.sayName();//xiaoming

原型链

function SuperType(){
  this.property = true;
}
SuperType.prototype.getSuperValue = function (){
  return this.property;
}
function SubType(){
  this.subproperty = false;
}
SubType.prototype = new  SuperType();
SubType.prototype.getSubValue = function (){
  return this.subproperty;
}

// 测试
var instance = new SubType();
instance.getSuperValue();// true
// 原理见下图
62C5E929-EB58-4A82-BE54-0D2191D8D44D.png
// 完整的原型链
93D05E75-F7D8-4E6C-A98F-4609D8B45684.png
// 确定原型和实例的关系
// 添加原型方法的代码一定放在替换原型之后
// 继承
function SuperType(name){
    this.name = name;
}
function SubType(){
  SuperType.call(this,"Nicholas");
  this.age = 23;
}
var instance = new SubType();
instance.name; // Nicholas
instance.age; // 23
// 组合继承
function SuperType(name){
   this.name = name;
   this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name)
}
function SubType(name,age){
  SuperType.call(this,name);
  this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
  alert(this.age);
}

var instance1 = new SubType("Nicholas",29);
instance1.colors.push("black");//red,blue,green,black
instance1.sayAge;//Nicholas
instance1.sayName;//29

var instance2 = new SubType("Grey",27);
instance2.colors;//red,blue,green
instance2.sayAge;//Grey
instance2.sayName;//27
// 原型式继承
//eg
function object(o){
  function F(){ }
  F.prototype = o;
  return new F();
}
var person = {
  name:"nick",
  friends:["shelby","court"];
};
var anotherObj = object(person);
anotherObj.name = "greg";
anotherObj.friends.push("rob");

var yeObj = object(person);
yeObj.name = "linda";
yeObj.friends.push("bar");
person.friend;//"shelby","court", rob, bar
// 上面的思想 在 js 有个方法规范了
// 重写为
var person = {
  name:"nick",
  friends:["shelby","court"];
};
var anotherObj = Object.create(person);
anotherObj.name = "greg";
anotherObj.friends.push("rob");

var yeObj = Object.create(person);
yeObj.name = "linda";
yeObj.friends.push("bar");
person.friend;//"shelby","court", rob, bar
// create 有两个参数,第二个参数与Object.defineProperties()方法的第二个参数作用一致,覆盖原型上的同名属性
// eg
var person = {
  name:"nick",
  friends:["shelby","court"];
};
var anotherObj = Object.create(person,{
  name:{
    value:"greg"
  }
});
anotherObj.name;//greg
// 寄生式继承(和原型式紧密)
function object(o){
  function F(){ }
  F.prototype = o;
  return new F();
}
function createrAnother(o){
  var clone = object(o);
 clone.sayHi = function (){
    alert("hi");
  }
  return clone;
}
// 使用
var person = {
  name:"nick",
  friends:["shelby","court"];
};
var anotherPerson = createrAnother(person);
anotherPerson.sayHi();// hi
// 寄生组合式
// 先看一个组合继承的例子
function SuperType(name){
  this.name = name;
  this.colors = ["red","green"];
}
SuperType.protoType.sayName = function (){
  alert(this.name);
}
function SubType(name,age){
  SuperType.Call(this,name);//第一次
  this.age = age;
}

SubType.prototype = new SuperType();//第二次
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function (){
  alert(this.age);
}
// 注意了 SuperType() 一共调用了两次
// 为解决调用了两次 SuperType() ,寄生组合应运而生
//上面代码的这一部分
SubType.prototype = new SuperType();//第二次
SubType.prototype.constructor = SubType;
// 可以替换为
function inheritPrototype(SubType,SuperType){
  var prototype = object(SubType, SuperType);// 创建
  prototype.constructor = SubType;// 增强
  SubType.prototype = prototype;// 制定
}
inheritPrototype(SubType, SuperType);//用这句话代替
// 替换的图示如下:
C92D62CF-F471-4EBC-8647-0569129C6734.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容