js 面向对象

主宰稳场 提交于 2020-03-08 07:55:20

1.对象的表现形式

// new Object()
var person = new Object();
person.name = "sdf";
person.age = "23";
person.job = "Enginer";
person.sayName = function () {
  alert(this.name)
}
person.sayName();

//对象字面量模式
var person = {
  name: "sdf",
  age: 23,
  job: "Enginer",
  sayAge: function () {
    alert(this.age)
  }
}
person.sayAge();

  Reflection:如果有多个对象需要创建多次

2.创建对象工厂模式

func:解决了创建多个对象模式,但是没有解决对象识别的问题(即怎么知道一个对象的类型)
function creatPerson(name, age, job) {
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayName = function () {
    alert(this.name)
  }
  return o;
}
var person1 = creatPerson("Nicholas", 23, "Enginer");
var person2 = creatPerson("Greg", 27, "Doctor");
person1.sayName()
person2.sayName()

3.构造函数模式

function Person(name, age, job){    
   this.name = name;
   this.age = age;
   this.job = job;
   this.sayName = function(){        
            alert(this.name);    
      }
}
var person1 = new Person("wei",25,"software");
var person2 = new Person("bu",25,"software");

(1)在这个例子中,Person()函数取代了createPerson()函数,我们注意到Person()与createPerson()的不同之处在于:

  • 没有显式的创建对象

  • 直接将属性和方法赋值给this对象

  • 没有return语句 

(2)这两个对象都有一个constructor(构造函数)属性,该属性指向Person。如下:

  console.log(person1.constructor == Person);     //true

  console.log(person2.constructor == Person);     //true

      对象的constructor属性最初是用来标识对象类型的。但是,提到检测对象类型,还是instanceof操作符比较可靠一些。我们在这个例子中创建的对象都是Object对象的实例,也是Person对象的实例,这一点通过instanceof操作符可以验证。

  console.log(person1 instanceof Object);     //true

  console.log(person1 instanceof Person);     //true

  console.log(person2 instanceof Object);     //true

  console.log(person2 instanceof Person);     //true

4.原型模式

  (1) 我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的实例就是让所有实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象的实例信息,而是可以将这些信息直接添加到原型对象中,如下所示:

// 构造函数
function Person() {};

Person.prototype.name = "asdas";
Person.prototype.age = 23;
Person.prototype.job = "Engineer";
Person.prototype.sayName = function () {
  console.log(this.name)
};

var person1 = new Person();
person1.name = "nikon"; //实例了属性
console.log(person1.name); //nikon来自实例
// delete person1.name; //删除实例值,返回原型值
console.log(person1.name); // asdas

var person2 = new Person()
console.log(person2.name) //来自原型*/asdas
person2.sayName();

console.log(person1.hasOwnProperty("name")); //判断属性是否来自实例 true

// 函数用于指示对象是否存在于另一个对象的原型链中。如果存在,返回true,否则返回false
// Person 是否存在于 person2 的原型链中
console.log(Person.prototype.isPrototypeOf(person1))
console.log(Object.getPrototypeOf(person1).name) //返回的对象实际就是这个对象的原型
console.log(person1 instanceof Person); // true; instanceof 是否是new出来的实例
console.log(person1.constructor === Person); // true; constructor指向构造函数

//结论
/*
1,原型模式省略了为构造函数传递参数的这一环节,结果所有实例在默认情况下都具有相同的属性值。
2,原型模式最大的问题是由其共享的本质所导致的。原型中所有的属性被很多实例共享
*/

  (2)简单原型

// 简单的原型
function Person() {};
Person.prototype = {
  //constructor : Person,// 之后就不属于object,属于person
  name: "Nikons",
  age: 23,
  job: "Engineer",
  sayName: function () {
    console.log(this.name)
  }
}
var p1 = new Person();
console.log(p1 instanceof Object); // true
console.log(p1 instanceof Person); // true
console.log(p1.constructor == Person); //false 已经无法确定对象类型
console.log(p1.constructor == Object); // true

  (3) 原型中所有的属性被很多实例共享

function Person() {}

Person.prototype = {
  constructor: Person,
  name: "小米",
  age: 20,
  friends: ["小强", "小明"],
  sayName: function () {
    alert(this.name);
  }
}

var person1 = new Person();
var person2 = new Person();
person1.friends.push("小黑");
console.log(person1.friends); //["小强", "小明", "小黑"]
console.log(person2.friends); //["小强", "小明", "小黑"]
console.log(person1.friends === person2.friends); //true

/*
上面的例子中,Person.prototype对象有一个名为friends的属性,该属性包含一个字符串数组。
然后创建了两个Person的实例,接着修改person1.friends引用的数组,向数组中添加一个字符串,由于数组存在于Person.prototype中而不是person1中。
所以person2.friends也会被修改。但是一般每个对象都是要有属于自己的属性的,所以我们很少看到有人单独使用原型模式来创建对象。
 */

  (4) 组合使用构造函数模式和原型模式

/*
创建自定义类型最常见的方式就是组合使用构造函数模式与原型模式。
构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。
结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存。
另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长。
*/

function Person() {
  this.name = "小米";
  this.age = 20;
  this.friends = ["小强", "小明"];
}

Person.prototype = {
  constructor: Person,
  sayName: function () {
    alert(this.name);
  }
}

var person1 = new Person();
var person2 = new Person();
person1.friends.push("小黑");
console.log(person1.friends); //["小强", "小明", "小黑"]
console.log(person2.friends); //["小强", "小明"]
console.log(person1.friends === person2.friends); //false

  (5)动态原型模式

    有其他OO语言经验的开发人员在看到独立的构造函数和原型时,很可能会感到非常的困惑。动态原型模式就是用来解决这个问题的一个方案,它把所有的信息都封装在了构造函数中,而通过构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否要初始化原型。来看一个例子:

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.friends = ["小黑", "小米"]; //注意if语句
  if (typeof this.sayName != "function") {
    Person.prototype.sayName = function () {
      alert(this.name);
    }
  }
}

var person1 = new Person("wei", 29);
person1.friends.push("小明");
person1.sayName();

/*
注意构造函数代码中的if语句,这里只在sayName()方法不存在的情况下才会将它添加到原型中。
这断代码只有在第一次调用构造函数的时候才会被执行。此后,原型已经被初始化,不需要再做什么修改。
不过要记住,这里所做的修改能立即在所有实例中得到反映。因此,这种方法可以说确实非常完美。
其中if语句检查的是初始化之后应该存在的任何方法和属性–不必再用一大堆if来检查每个属性和方法,只检查其中一个即可。
对于采用这样模式创建的对象,还可以使用instanceof操作符来确定他的类型。
*/

  

  注意:使用动态原型模式时,不能使用对象字面量重写原型。如果在已经创建了实例的情况下重写原型,那么就会切断现有的实例与新原型之间的联系。

   (6) 寄生构造函数模式

function Person(name, age, job) {
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayName = function () {
    alert(this.name);
  }
  return o;
}
var person = new Person("wei", 29, "banzhuan");
person.sayName(); //"wei"

/*
在这个例子中,Person函数创建了一个对象,并以相应的属性和方法初始化该对象,然后返回了这个对象。
除了使用new操作符把使用的包装函数叫做构造函数之外,这个模式和工厂模式并没有多大的区别。
构造函数在不返回值的情况下,会默认返回新对象的实例。而通过在构造函数的末尾添加一个return语句,可以重写调用构造函数时返回的值。
*/

  (7) 稳妥构造函数模式

  所谓稳妥对象,是指没有公共属性,而且其方法也不引用this对象。稳妥对象最适合在一些安全环境中(这些环境会禁止使用this和new),或者在防止数据被其他应用程序改动时使用。稳妥构造函数遵循的与寄生构造函数类似的模式,但又两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。按照稳妥构造函数的要求,可以将前面的Person构造函数重写如下:

function Person(name, age, job) {
  //创建要返回的新对象    
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  //可以在这里定义私有变量和函数    

  //添加方法
  o.sayName = function () {
    alert(this.name);
  };

  //返回对象
  return o;
}
// 注意, 在以这种模式创建的对象中, 除了使用sayName() 方法之外, 没有其他办法访问name的值。 可以像下面使用稳妥的Person构造函数:

var person = Person("xiaoming", 21, "banzhuan");
person.sayName(); //xiaoming

  

  这样,变量person中保存的是一个稳妥对象,而除了sayName()方法外,没有别的方式可以访问其他数据成员。即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据。稳妥构造函数模式提供的这种安全性,使得他非常适合在某些安全执行环境–例如,ADsafe(www.adsafe.org)提供的环境下使用。 

        注意:与寄生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间没有什么关系,因此instanceof操作符对这种对象也没有意义。

 

5.原型链的理解

在 ECMAScript 中,每个由构造器创建的对象拥有一个指向构造器 prototype 属性值的 隐式引用(implicit reference,这个引用称之为 原型(prototype。进一步,每个原型可以拥有指向自己原型的 隐式引用(即该原型的原型),如此下去,这就是所谓的原型链(prototype chain (参考资源)。在具体的语言实现中,每个对象都有一个 __proto__ 属性来实现对原型的 隐式引用。程序清单 4说明了这一点。
清单 4. 对象的 __proto__ 属性和隐式引用

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person( name ) {
    this.name = name;
}
var p = new Person();
// 对象的隐式引用指向了构造器的 prototype 属性,所以此处打印 true
console.log( p.__proto__ === Person.prototype );
 
// 原型本身是一个 Object 对象,所以他的隐式引用指向了
// Object 构造器的 prototype 属性 , 故而打印 true
console.log( Person.prototype.__proto__ === Object.prototype );
 
// 构造器 Person 本身是一个函数对象,所以此处打印 true
console.log( Person.__proto__ === Function.prototype );

有了 原型链,便可以定义一种所谓的 属性隐藏机制,并通过这种机制实现继承。如下:

// 声明 Animal 对象构造器
function Animal() {
}
// 将 Animal 的 prototype 属性指向一个对象,
// 亦可直接理解为指定 Animal 对象的原型
Animal.prototype = {
    name: "animal",
    weight: 0,
    eat: function() {
        alert( "Animal is eating!" );
    }
}
// 声明 Mammal 对象构造器
function Mammal() {
    this.name = "mammal";
}
// 指定 Mammal 对象的原型为一个 Animal 对象。
// 实际上此处便是在创建 Mammal 对象和 Animal 对象之间的原型链
Mammal.prototype = new Animal();
 
// 声明 Horse 对象构造器
function Horse( height, weight ) {
    this.name = "horse";
    this.height = height;
    this.weight = weight;
}
// 将 Horse 对象的原型指定为一个 Mamal 对象,继续构建 Horse 与 Mammal 之间的原型链
Horse.prototype = new Mammal();
 
// 重新指定 eat 方法 , 此方法将覆盖从 Animal 原型继承过来的 eat 方法
Horse.prototype.eat = function() {
    alert( "Horse is eating grass!" );
}
// 验证并理解原型链
var horse = new Horse( 100, 300 );
console.log( horse.__proto__ === Horse.prototype );
console.log( Horse.prototype.__proto__ === Mammal.prototype );
console.log( Mammal.prototype.__proto__ === Animal.prototype );

6. JavaScript 私有成员实现

  JavaScript 没有实现面向对象中的信息隐藏,即私有和公有。与其他类式面向对象那样显式地声明私有公有成员的方式不同,JavaScript 的信息隐藏就是靠闭包实现的。

 

// 声明 User 构造器
function User( pwd ) {
  // 定义私有属性
  var password = pwd;
  // 定义私有方法
  function getPassword() {
      // 返回了闭包中的 password
      return password;
  }
  // 特权函数声明,用于该对象其他公有方法能通过该特权方法访问到私有成员
  this.passwordService = function() {
      return getPassword();
  }
}
// 公有成员声明
User.prototype.checkPassword = function( pwd ) {
  return this.passwordService() === pwd;
};
// 验证隐藏性
var u = new User( "123456" );
// 打印 true
console.log( u.checkPassword( "123456" ) );
// 打印 undefined
console.log( u.password );
// 打印 true
console.log( typeof u.gePassword === "undefined" );

  JavaScript 必须依赖闭包实现信息隐藏,是由其函数式语言特性所决定的。本文不会对函数式语言和闭包这两个话题展开讨论,正如上文默认您理解 JavaScript 中基于上下文的 this 一样。

  面向对象组件化扩展demo

 

 

 

.

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!