JS中对象的声明和继承

159 阅读4分钟

JS中对象的声明

1. 字面量形式直接声明

var p={
  name:"zhangsan",
  sayName:function(){
    console.log(this.name);
  }
}
p.sayName();
  • 优点:声明单个对象时方便快捷
  • 缺点:声明大量同类型对象时会产生大量冗余

2. 工厂模式

function person(name){
  var o=new Object();
  o.name=name;
  o.sayName=function(){
    console.log(o.name);
  }
  return o;
}
var p=person("zhangsan");
p.sayName();
  • 优点:声明多个同类型的对象时根简洁
  • 缺点:没有办法区分同一类型的对象(例如使用instance)

3. 构造函数模式

function Person(name){
  this.name=name;
  this.sayName=function(){
    console.log(this.name);
  }
}
var p=new Person("zhangsan");
p.sayName();
var p1=new Person("zhangsan");
var p2=new Person("lisi");
console.log(p1.sayName===p2.sayName);     //false
  • 优点:可以声明多个特定类型的对象
  • 缺点:如果对象中有方法,每次声明一个对象都会创建一个新的方法实例,从而造成冗余

4. 原型模式

function Person(){};
Person.prototype.name="zhangsan";
Person.prototype.sayName=function(){
  console.log(name);
}
var p=new Person();
p.sayName();
var p1=new Person("zhangsan");
var p2=new Person("lisi");
console.log(p1.sayName===p2.sayName);     //true
  • 优点:对象中的方法是所有对象共有的(属性和方法都是共享的),从而不会造成冗余
  • 缺点:对象无法拥有自己的私有属性

5. 组合使用构造函数和原型模式

function Person(name){
  this.name=name;
}
Person.prototype.sayName=function(){
  console.log(name);
}
var p=new Person("zhangsan");
p.sayName();
  • 优点:对象既可以拥有自己的私有属性,也可以共享方法

6. 动态模式

function Person(name){
  this.name="zhangsan";
  if(typeof sayName!="function"){
    Person.prototype.sayName=function(){
      console.log(name);
    }
  }
}
var p=new Person("zhangsan");
p.sayName();
var p1=new Person("zhangsan");
var p2=new Person("lisi");
console.log(p1.sayName===p2.sayName);
  • 优点:在组合使用构造函数和原型模式的基础上将方法的定义放在了构造函数内部,使得看起来更整洁

7. 寄生模式(不推荐)

function Person(name){
  var o=new Object();
  o.name=name;
  o.sayName=function(){
    console.log(name);
  }
  return o;
}
var p=new Person("zhangsan");
p.sayName();

除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式和工厂模式一模一样。

  • 优点:通过寄生模式可以给别的引用类型添加值或方法,例如给Array引用类型添加返回数组所有项,并且用|隔开的方法。
  • 缺点:没有办法区分同一类型的对象(例如使用instance)

8. 稳妥模式

function Person(name){
  var o=new Object();
  var name=name;
  o.sayName=function(){
    console.log(name);
    return name;
  }
  return o;
}
var p=new Person("zhangsan");
p.sayName();

构造函数内不使用this,不使用new操作符调用构造函数

  • 优点:该构造函数内定义的属性,只能通过该构造函数的方法访问,从而保证了安全性。例如,要访问name,只能通过sayName方法。
  • 缺点:没有办法区分同一类型的对象(例如使用instance)

9. ES6中对象的声明

class Person{
  constructor(name){
    this.name=name;
  }
  sayName(){
    console.log(this.name);
  }
}
var p=new Person("zhangsan");
p.sayName();

JS中对象的继承

1. 原型链直接继承

function Person(name){        //声明父类Person
  this.name=name;
}
Person.prototype.sayName=function(){
  console.log(this.name);
}

function Student(school){   //声明子类Student
  this.school=school;   
}
Student.prototype.saySchool=function(){
  console.log(this.school);
}
Student.prototype=new Person("zhangsan");    //将子类的原型链赋值为父类的一个实例
  • 缺点:创建子类时不能向父类中传递参数,并且父类中的变量在子类中是所有实例共享的
var stu1=new Student("PKU");
stu1.sayName();          //zahngsan
stu1.saySchool();        //PKU
var stu2=new Student("THU");
stu2.sayName();          //zhangsan
stu2.saySchool();        //THU

实例stu1和实例stu2的name属性都是zhangsan,无法为每个实例设置不同的name。

2. 调用构造函数实现继承

function Person(name){        //声明父类Person
  this.name=name;
  this.sayName=function(){    
    console.log(name);
  }
}
function Student(name,school){   //声明子类Student
  Person.call(this,name);     //当执行Student的构造函数时,也会把Person的构造函数执行一遍
  this.school=school;
  if(typeof saySchool!="function"){
    Student.prototype.saySchool=function(){
      console.log(school);
    }
  }
}
var stu1=new Student("zhangsan","PKU");
stu1.sayName();         //zhangsan
stu1.saySchool();       //PKU
var stu1=new Student("lisi","THU");
stu1.sayName();         //lisi
stu1.saySchool();       //THU
  • 优点:创建子类的实例时可以赋值父类的变量
  • 缺点:
    1. 父类中的方法不能定义在原型上以实现复用,因为在Student的构造函数中执行 Person.prototype.sayName=function(){console.log(this.name);} 会在Person上定义sayName方法,与Student没有关系
    2.父类的原型上定义的方法,对子类而言是不可见的

3.组合原型链和调用构造函数的方法实现继承

function Person(name){        //声明父类Person
  this.name=name;
}
Person.prototype.sayName=function(){
  console.log(this.name);
}
function Student(name,school){   //声明子类Student
  Person.call(this,name)         //在子类中执行一遍父类的构造函数
  this.school=school;   
}
Student.prototype=new Person();    //将子类的原型链赋值为父类的一个实例
Student.prototype.constructor=Student;  //将Student的原型的constructor属性赋值为Student,否则为Person
Student.prototype.saySchool=function(){    
  console.log(this.school);
}
var stu1=new Student("zhangsan","PKU");
stu1.sayName();         //zhangsan
stu1.saySchool();       //PKU
var stu1=new Student("lisi","THU");
stu1.sayName();         //lisi
stu1.saySchool();       //THU
  • 优点:既可以实现方法的复用,又可以在创建子类的实例时赋值父类的变量
  • 缺点:需要调用两次父类的构造函数,一次在子类的构造函数内部,一次在赋值子类的原型链时

4.ES6中的继承

class Person{            //声明父类
  constructor(name){
    this.name=name;
  }
  sayName(){
    console.log(this.name);
  }
}
class Student extends Person{   //声明子类,并使用extends来表示要继承哪个类
  constructor(name,school){
    super(name);                //调用父类的构造函数
    this.school=school;
  }
  saySchool(){
    console.log(this.school);
  }
}
var stu1=new Student("zhangsan","PKU");
stu1.sayName();
stu1.saySchool();
var stu2=new Student("lisi","THU");
stu2.sayName();
stu2.saySchool();