JS 面向对象

js 面向对象

对象理解

属性类型

属性定义方法:ie8不支持

  • Object.defineProperty(obj, propertyName, 包含修改内容的对象)

    • 数据属性的四个特征:
      [[Configurable]] 可否通过delete删除属性并重新定义
      [[Enumerable]] 可否枚举/通过for-in返回属性
      [[Writable]] 可写
      [[Value]]
    • 修改访问器属性的两个函数
      gettersetter 函数
  • Object.definePropertyies(obj, {修改的属性})

特性读取

Object.getOwnPropertyDescriptor()---返回一个对象

创建对象模式

工厂模式

定义函数,接收参数作为属性值,新建一个对象,并返回赋值后的对象。

function createCat(name, age, color){
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.color = color;
  return obj;
}
var cat1 = createCat("mimi", 2, "yellow");

构造函数模式

function Cat(name, age, color){
    this.name = name;
    this.age = age;
    this.color = color;
    this.getName = function(){ return this.name; };
}
var cat2 = new Car("lucky", 1, "white");
var cat3 = new Car("cathy", 1, "black");

!!!new 操作符的步骤:

  1. 创建一个空对象
  2. this 指向新对象(构造函数的作用域赋给这个对象)
  3. 执行构造函数给对象添加属性
  4. 返回新对象

问题:
每个实例都会新建 Function 实例,实例的机制相同,但是作用域和标识符解析不同,是不必要的开销。
解决:将方法定义在外部。

function Cat(name, age, color){
    this.name = name;
    this.age = age;
    this.color = color;
    this.getName = getName;
}
function getName()
{ 
    return this.name;
}

原型模式

function Person(){};
Person.prototype.name = "jack";

通过 Person.prototype 可以给原型添加属性和方法,也可以通过定义对象字面量一次添加多个,但是会破坏 constructor 指向,可以再修改回来。而且通过定义对象字面量重写原型对象会导致原型链断开:

function Person(){};
var person = new Person();//先实例化
Person.prototype = {//再重写原型对象
    constructor: Person,
    name:john,
    age: 20,
    getName: function(){}
};
//person.getName()会报错,因为person指向的是旧的原型对象,其中没有这个方法

组合模式

在构造函数中定义非引用数值属性,在原型中定义共享属性 constructor 和方法。

function Person(name, age, eyeColor){
    this.name = name;
    this.age = age;
    this.eyeColor = eyeColor;
};
Person.prototype = {
    constructor: Person,
    getName: function(){}
};

动态原型模式

function Person(name, age, eyeColor){
    this.name = name;
    this.age = age;
    this.eyeColor = eyeColor;
    if(typeof  this.getName != "function"){//只有getName不存在的时候才会调用,就是初次调用构造函数的时候
        Person.prototype.getName = function(){};
    }
};

寄生构造函数模式

function createCat(name, age, color){
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.color = color;
  return obj;
}
var cat1 = new createCat("mimi", 2, "yellow");

对象和构造函数无关系

稳妥构造函数模式

原型与原型链

原型对象

  • 创建一个函数时会自动创建一个原型对象,只有函数拥有 prototype 属性,【prototype】指向函数的【原型对象】
  • prototype 原型对象中有【constructor属性】指回向【构造函数】。
  • : ES6 的箭头函数没有 prototype 属性,但是有 __proto__ 属性。

原型

  • prototype 是【基于构造函数构造的实例对象】的隐式原型([[prototype]],部分浏览器可以通过 __proto__ 属性访问),即 [[prototype]] 指向【其构造函数的原型对象】。

    • 从构造函数A实例化对象a,那么【a的原型】就是【A的原型对象prototype】。
  • 所有的引用类型都有 __proto__ 属性

原型链与继承

原型链是继承的一种实现方法:利用原型使【引用类型A】继承【引用类型B】的属性和方法
引用类型值属性会被所有实例共享

  • 有构造函数A、B,B继承自A,C为B的一个实例
    【实例 C 的 __proto__ 】指向【B prototype 原型对象】,【B prototypeconstructor 属性】指向【构造函数B】,【B prototype__proto__ 】指向【A prototype 原型对象】。

实际上是 __proto__ 连接【实例】和【原型对象】形成原型链。

  • C.__proto__ === B.prototype //true
  • C.__proto__.__proto__ === A.prototype //true
//没有修改 constructor
function Animal(){
    this.name = 'animal';
}
function Cat(){
    this.name = 'Cat';
}
function ChinaCat(){
    this.name='chinacat';
}
Cat.prototype = new Animal();   //继承Animal
ChinaCat.prototype = new Cat(); //继承Cat
//插入 constructor 修改语句
var a = new ChinaCat();

结果如图:

此时

  • 【实例a】, 【ChinaCat原型对象】,【Cat原型对象】的 constructor 都会指向【function Animal(){}
    因为 a.__proto__ 指向 ChinaCat 原型对象,ChinaCat 原型对象的__proto__ 指向 Cat 原型对象,Cat 原型对象的__proto__ 指向 Animal 原型对象,Animal 原型对象中的 constructor 指向【function Animal(){}】.
    注:实例 a 的 prototype 为空,因为 prototype 是函数有的属性
//插入constructor修改语句
Cat.prototype.constructor = Cat;
ChinaCat.prototype.constructor = ChinaCat;
var a = new ChinaCat();

此时

  • a.constructor 】指向其【构造函数 function ChinaCat(){} 】,【a.__proto__ 】等于其原型对象中的__proto__ 所指向的【Cat原型对象】,
  • ChinaCat原型对象】

ES5继承

绑定构造函数

缺点:父类型中定义的方法不可继承,对子类型不可见,只能通过构造函数,无法进行函数复用。

function Parent(){
	this.color = ['red','blue','yellow'];
}
function Child(){
	parent.call(this); //parent.apply(this,arguments);
}
var ins = new Child();
ins.color.push('white');//ins.color = ['red','blue','yellow','white'];

原型链继承

缺点:无法向父类构造函数中传递参数;子类原型链上定义的方法有先后顺序问题;引用类型值的原型属性会被共享。
需要注意的一点是要修改constructor的指向。

function Animal(species) {
    this.species = species;
}
Animal.prototype.func = function() {
    console.log("Animal");
};
function Cat() {}
/*func方法是无效的, 因为后面原型链被重新指向了Animal实例*/
Cat.prototype.func = function() {
    console.log("Cat");
};
Cat.prototype = new Animal();//原型继承
//Cat.prototype.constructor属性会指向Animal,而不是Cat,需要手动修改
Cat.prototype.constructor = Cat; // 修复: 将Cat.prototype.constructor重新指向Cat
var cat = new Cat();
cat.func(); // output: Animal
console.log(cat.species); // undefined,不能传递参数

组合继承

  • 用原型链实现对原型属性和方法的继承
  • 借用构造函数实现对实例属性的继承。

缺点:调用了两次父类构造函数

function Animal(type){//构造函数,定义属性
    this.type = type;
    this.deployment = [];
}
Animal.prototype.getType= function(){//原型对象定义方法
    return this.type;
};
//继承属性
function Cat(name, age){//构造函数,继承并定义自己的属性
    Animal.call(this, "cat"); 
    this.name = name;
    this.age = age;
}
//继承方法
Cat.prototype = new Animal();   //第一次调用   获得Animal的实例属性type\deployment
Cat.prototype.constructor = Cat;    //修改constructor指向
//实例化对象
var cat1 = new Cat("lucky",2);//第二次在Cat构造函数中调用  会重新获得实例属性,从而屏蔽原型中的同名属性
cat1.deployment.push("china");
var cat2 = new Cat("lucy",1);//第二次在Cat构造函数中调用  会重新获得实例属性,从而屏蔽原型中的同名属性
cat2.deployment.push("canada");
//
console.log(cat1);
console.log(cat2);

实际上,就有了两组相同的属性:一组在cat1/cat2实例中,一组在Cat原型对象中。

寄生组合式继承

解决组合继承的问题,是理想的继承方法

  • 用构造函数继承属性
  • 通过原型链混成形式继承方法
function inherentPro(subType, superType){
    var prototype = object(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}
inherentPro(Cat, Animal);//替换Cat.prototype = new Animal(); 
posted @ 2020-03-21 15:05  秋秋秋白  阅读(289)  评论(0编辑  收藏  举报