js面向对象的理解

2018-03-30  本文已影响30人  stanJ

属性理解

  1. 数据属性的4个特性

    • configurable: 表示能否用delete删除属性,能否修改属性的其他特性,能否把属性修改为访问器属性
    • enumerable: 表示能否通过for-in循环返回属性
    • writable: 表示能否修改属性的值
    • value:用来表示属性的值
  2. 修改数据属性默认特性的方法:Object.defineProperty(对象名, 属性名,描述符对象)
    举例:

var person = {};
// 该方法configurable,enumerable,writable的默认值是false(意思是说不传值的话默认会传false)
// value的默认值是undefined
// 将configurable值改为false后,经chrome59测试后发现改了之后所有特性值修改都会报错(当然如果传入的新值和旧值一样的话就不会报错)
// 不像红宝书里说的writable值还可以修改,实际上都不可修改
Object.defineProperty(person, 'name', {
    configurable: false,
    enumerable: true,
    writable: true,
    value: 'Stan'
});
// 小tip:使用chrome控制台查看对象属性时,有时会发现有些属性颜色比较淡,类似disable的状态,实际是因为该属性的enumerable特性为false

  1. 访问器属性的4个特性
    • configurable: 表示能否用delete删除属性,能否修改属性的其他特性,能否把属性修改为访问器属性
    • enumerable: 表示能否通过for-in循环返回属性
    • get: 读取属性时调用的函数,默认undefined
    • set: 写入属性时调用的函数,默认undefined
  2. 定义多个属性的方法Object.defineProperties()
    举例:
var book = {}; 
Object.defineProperties(book, { 
    _year: { 
        value: 2004 
    }, 
    edition: { 
        value: 1 
    }, 
    year: { 
        get: function(){ 
            return this._year; 
        }, 
        set: function(newValue){ 
            if (newValue > 2004) { 
                this._year = newValue; 
                this.edition += newValue - 2004; 
            } 
        } 
    } 
}); 
  1. 读取属性特性的方法Object.getOwnPropertyDescriptor
    举例:
var descriptor = Object.getOwnPropertyDescriptor(book, '_year');
alert(descriptor.value);
alert(descriptor.configurable);

创建对象

  1. 工厂模式
// 工厂模式解决了创建多个相似对象的问题,但没有解决对象识别的问题

function createPerson (name, age, job) {
    var o = {
        name: name,
        age: age,
        job: job,
        sayName: function () {
            alert(this.name);
        }
    }
    return o;
}

var person1 = createPerson('Stan', 25, 'Web front-ender');
var person2 = createPerson('Danny', 18, 'backender');
  1. 构造函数模式
// 构造函数模式解决了对象类型识别的问题,但不同实例的方法并不是同一个Function实例

function Person (name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function () {
        alert(this.name);
    }
}

var person1 = new Person('Stan', 25, 'Web front-ender');
var person2 = new Person('Danny', 18, 'backender');
person1.sayName === person2.sayName // false

// 把函数定义从构造函数里移出来,解决了同一个函数的问题,但全局函数的存在让其毫无封装性可言

function Person (name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName
}

function sayName () {
    alert(this.name);
}

var person1 = new Person('Stan', 25, 'Web front-ender');
var person2 = new Person('Danny', 18, 'backender');
person1.sayName === person2.sayName // true
  1. 原型模式

3.1 理解原型对象
任何一个函数都有一个prototype属性,该属性的值是一个原型对象。这个原型对象自动拥有一个constructor属性,属性值为这个函数。开发者可以给函数的原型对象添加属性和方法。使用这个函数创建的实例拥有__proto__属性(Firefox、Safari、Chrome支持),该属性值指向构造函数的原型对象。举例:

function Person () {
    
};
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function(){ 
    alert(this.name); 
}; 
var person1 = new Person(); 
person1.sayName(); //"Nicholas" 
var person2 = new Person();
person2.sayName(); //"Nicholas" 
alert(person1.sayName == person2.sayName); //true 

// 确定函数原型对象和实例之间的关系
alert(Person.prototype.isPrototypeOf(person1)); //true 
alert(Person.prototype.isPrototypeOf(person2)); //true 
// es5
alert(Object.getPrototypeOf(person1) == Person.prototype); //true 
alert(Object.getPrototypeOf(person1).name); //"Nicholas" 

// 对象实例添加的同名属性会屏蔽对象实例的原型对象中的属性,因为属性值会先从对象实例的属性里面查找,找不到才会去原型对象里面去找,想解除屏蔽,只有delete掉那个属性
// 在chrome59中可以通过修改person1.__proto__对象改变原型对象的值,与红宝书所说不符
person1.name = "Greg"; 
alert(person1.name); //"Greg"——来自实例
alert(person2.name); //"Nicholas"——来自原型
delete person1.name; 
alert(person1.name); //"Nicholas"——来自原型

// 判断属性是不是对象实例的
alert(person1.hasOwnProperty("name")); //false 
person1.name = "Greg"; 
alert(person1.name); //"Greg"——来自实例
alert(person1.hasOwnProperty("name")); //true 
alert(person2.name); //"Nicholas"——来自原型
alert(person2.hasOwnProperty("name")); //false 
delete person1.name; 
alert(person1.name); //"Nicholas"——来自原型
alert(person1.hasOwnProperty("name")); //false 

// in操作符单独使用可以用来判断某属性是否存在于对象实例或原型对象中
alert('name' in person1)

// for-in循环返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性
person1.age = 20;
for (let k in person1) {
  console.log(k + ':' + person1[k])
}
// age:20
// name:Nicholas
// job:Software Engineer
// sayName:function (){ 
//   alert(this.name); 
// }

// 想要获取对象上所有可枚举的实例属性,可以使用Object.keys()
var keys = Object.keys(Person.prototype); 
alert(keys); //["name", "age", "job", "sayName"]
var p1 = new Person(); 
p1.name = "Rob"; 
p1.age = 31; 
var p1keys = Object.keys(p1); 
alert(p1keys); //["name", "age"]

// 想要获取对象上所有实例属性,无论是否是可枚举的,可以用Object.getOwnPropertyNames(),__proto__获取不到
var keys = Object.getOwnPropertyNames(Person.prototype); 
alert(keys); //["constructor", "name", "age", "job", "sayName"]

/********************************************************************************/
/*
 * 原型的动态性
 */
// 即使创建实例在添加方法之前,但只要调用放在后面,都可以成功执行
var friend = new Person(); 
Person.prototype.sayHi = function(){ 
 alert("hi"); 
}; 
friend.sayHi(); //"hi"(没有问题!)

// 但是重写整个原型对象的话就不一样了,这时现有实例的__proto__属性值指向的是以前的原型对象,而以前的原型对象是没有sayName方法的
function Person(){ 
} 
var friend = new Person(); 
Person.prototype = { 
  constructor: Person, 
  name : "Nicholas", 
  age : 29, 
  job : "Software Engineer", 
  sayName : function () { 
    alert(this.name); 
  } 
}; 
friend.sayName(); //error 

/*
 * 原生对象的原型
 */
// 虽然可以给原生构造函数的原型对象添加方法,但不推荐在生产环境使用,有可能命名冲突

/*
 * 原型模式的缺点
 */
// 原型模式的优势在于共享,但缺点也是由于共享导致的。如下,引用类型使得对一个实例的值做出改变,其他所有实例的值都会发生改变
function Person(){ 
} 
Person.prototype = { 
  constructor: Person, 
  name : "Nicholas", 
  age : 29, 
  job : "Software Engineer", 
  friends : ["Shelby", "Court"], 
  sayName : function () { 
    alert(this.name); 
  } 
}; 
var person1 = new Person(); 
var person2 = new Person(); 
person1.friends.push("Van"); 
alert(person1.friends); //"Shelby,Court,Van" 
alert(person2.friends); //"Shelby,Court,Van" 
alert(person1.friends === person2.friends); //true 
  1. 组合使用构造函数模式和原型模式,这样的话将和数值有关的放在构造函数里面,将方法放在原型对象里面,举例:
function Person(name, age, job){ 
  this.name = name; 
  this.age = age; 
  this.job = job; 
  this.friends = ["Shelby", "Court"]; 
} 
Person.prototype = { 
  constructor : Person, 
  sayName : function(){ 
    alert(this.name); 
  } 
} 
var person1 = new Person("Nicholas", 29, "Software Engineer"); 
var person2 = new Person("Greg", 27, "Doctor"); 
person1.friends.push("Van"); 
alert(person1.friends); //["Shelby", "Court", "Van"]
alert(person2.friends); //["Shelby", "Court"]
alert(person1.friends === person2.friends); //false 
alert(person1.sayName === person2.sayName); //true 
  1. 寄生构造函数模式和稳妥构造函数模式
    没有看出这两种模式有什么实际意义,仅记录

继承

  1. 组合继承
    是将原型链和借用构造函数的技术组合到一块的继承方式,举例:
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.constructor = SubType;
SubType.prototype.sayAge = function() {
  alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //["red", "blue", "green", "black"]
instance1.sayName(); //"Nicholas"; 
instance1.sayAge(); //29 
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //["red", "blue", "green"]
instance2.sayName(); //"Greg"; 
instance2.sayAge(); //27
  1. 原型式继承
    只是用来创建一个类似对象
function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"

// 可用Obect.create()实现类似功能
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person, {
  name: {
    value: "Greg",
    enumerable: false
  }
});

alert(anotherPerson.name); //"Greg"
  1. 寄生组合式继承
    最有效的方式
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;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
  alert(this.age);
};

var instance = new SubType('Stan', 25);
alert(instance);

function inheritPrototype (subType, superType) {
  var prototype = object(superType.prototype);
  prototype.constructor = subType;
  subType.prototype = prototype;
}
function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

上一篇下一篇

猜你喜欢

热点阅读