由对象到原型

844 阅读7分钟

强大的对象

众所周知,在面试中我们经常会被面试官问到js的基本类型是什么,这个问题虽然基础但也正因为如此从而很考验求职者的基础。ok,那如果求职者回答出来了并且不想再考验他基础了,那就可以换一个角度问他,js中最牛掰的类型是什么?没错,答案就是除了基本类型之外的复杂类型 => Obejct。那么接下来我要说明的,无非两个问题,也许也是你看到这里心中所想的:

  • 1.到底啥是对象
  • 2.对象为什么牛掰

啥是对象?

  • 官方回答:无序属性的集合,其属性可以包含基本 值、对象或者函数
  • 最简单的来说:键值对,其中值可以是数据或者函数
//一个简单的例子
var person = new Object()
person.name = 'xiaoMing'
person.say= function(){
    console.log('my name is',this.name)
}

对象为啥牛掰

回归本质,想想编程世界,就是由两大核心数据与代码组成。数据本是静态,而编程的世界确实五彩缤纷多样的,因此代码必须具备描述与操作数据的能力。而后基于此,各种各样的编程思想编程库编程框架,都是在以自己的方式更好地去提升这种能力(更快地操作数据、更优雅更高效地描述展示数据等)的关系。再回头看看我上面写的对象吧,它就是代码具备这种能力的最好证明,也是javascript精巧的重要原因,name是'xiaoMing'(描述数据),函数say输出name(操作数据)。对象一下子就把代码的能力给展现出来了,还不牛掰吗?反正我觉得挺牛掰,如果你不觉得,继续往下看哈哈哈。。

新的对象创建方式

我们看到上面的代码都是new Object的方式创建对象(也就是所谓的工厂模式),那创立了多个对象要如何区分呢?或者说如何分类的?能不能都是“人”的对象我们都写成,new People,都是“动物”的对象我们都写成new Animal,而不都是new Object?构造函数应运而生,也就是高程书上讲的创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型

function Person(){
   this.name = name;
   this.say = function(){
       console.log('my name is',this.name)
   }
}
var xiaoMing = new Person('xiaoMing');
var xiaoHong = new Person('xiaoMing');
//xiaoMing,xiaoHong都是Person

原型的到来

又有一个问题,我们看到上面的代码两个实例xiaoMing和xiaoHong都有一个相同的say方法,但是通过上面的方式该方法被创建了两次,有没有办法优化呢?有,我们创造一个原型,把这个方法都放到上面去,大家都引用这个方法,那么不是ok了?你创建多少实例都无所谓啊。原型是一个更为独立、复用、封装、可继承、可多态的强大对象

function Person(){
   this.name = name;
   this.say = function(){
       console.log('my name is',this.name)
   }
}

Person.prototype.say = function(){
       console.log('my name is',this.name)
}
var xiaoMing = new Person('xiaoMing');
var xiaoHong = new Person('xiaoHong');

那么在代码中用什么来表示原型,又用什么来指向原型呢?答案就是Prototype和proto。Person的prototype挂了所有实例共有的方法与属性,xiaoMing等实例的_proto_.属性指向原型。Person本事就作为constructor.

注意:我们一般不在原型上挂属性,因为如果是一个包含引用类型值(诸如数组)的属性时,原型上的属性不会被屏蔽,改动结果会影响到别的实例

function Person(){}
Person.prototype.friends = ['xiaoGang'];

var xiaoMing = new Person();
var xiaoHong = new Person();
xiaoMing.friends.push('xiaoWang')
console.log(xiaoHong.friends) //["xiaoGang", "xiaoWang"]

原型链

因为一个实例可能是有不同的对象慢慢衍生出来的,比如最初是Object,再到构造函数Person,再到某个实例,所以啊每个实例会有一条_proto_链(_ proto _ . _ proto _ .toString),这个链就是原型链了。

原型的继承

基于原型链,我们其实可以拓展一个功能,那就是既然_proto_可以指向不同原型,那么我们 是否可以去指定它指向多个我们所希望的原型呢?这就是继承。举个生活中实际的例子,比如你,既继承于你的爸爸,也继承于你的妈妈,因此你可以既眼睛长得像爸爸,鼻子长得像妈妈。来看看代码中的实例:

    function Person(name){
      this.name = name;
    }
    Person.prototype.say = function(){
      console.log('my name is',this.name)
    }

    function Student(num,name){
      this.studentNum = num;
      Person.call(this,name) //借用构造函数传递属性
    }

    Student.prototype = new Person();
    Student.prototype.learn = function(){
      console.log('I am learning ...')
    }
    //纠正构造函数
    Student.prototype.constructor =Student;
    
    var xiaoMing = new Student(2,'xiaoMing');
    xiaoMing.say() //my name is xiaoMing
    xiaoMing.learn(); //I am learning ...

上述代码中xiaoMing既继承了Person,又继承了Student,因此他又可以say又可以Learn。同时值得注意的是,我们 借用了构造函数传递了属性值。如此一来,原型的世界是不是瞬间被放大了很多呢?

  • 其他的原型继承模式 上述是一种比较经典的原型继承方式,但发展至今,还有一些其他的方式,但本质都是一样,创建传递一个已经指向所想要继承的原型,的_proto_属性
    • 道格拉斯克劳福德(2006):
    function obejct(o){
      function F(){}
      F.prototype = o;
      //也可以在这里做一些扩展
      F.prototype.newFn = function(){}
      return new F();
    }
    Student.prototype = obejct(Person.prototype);
    
    • 上述例子演化而来的ES5 API之Obejct.create
    Student.prototype = Object.create(Person.prototype)
    

ES6-新时代继承

到了ES6时代,继承变得更加优雅简洁了

    class People {
      constructor(name){
        this.name  = name
      }
      say(){
        console.log('my name is',this.name)
      }
    }

    class Student extends People{
      constructor(props){
        super(props)
      }

      learn(){
        console.log('i am  learning...')
      }
    }

    var xiaoMing = new Student('xiaoMing');
    xiaoMing.say()  //my name is xiaoMing

虽说ES6这个语法有点像个语法糖,但仍有几点值得说明注意:

  • 1.子类中的super会调用父类中的构造函数,这也是为什么子类可以直接继承父类中的属性
  • 2.必须要在子类中调用super,才可以在子类中使用this
  • 3.super关键字还可以调用父类上的方法

此外,其实当一种语法已经‘语法糖’到让人可以毫无阻碍在开发中使用甚至舍弃之前API,那么已经不再是‘语法糖’了,更像是一个只是跟之前类似、却更为成熟的新型API

说完ES6,是时候说说“”了,因为它实在让人联系到“类”,特别是“class”这个关键字。我的个人感悟是,像“类”,因为我们之前也提到了 ,构造函数与工厂模式的重要差别就是给不同的对象一个标示,同时也可以进行归类。但 又不像“类”,在javascript中包括我们本文提到的一切 都是围绕对象所展开,这是一种基于对象的编程语言,甚至你所联想到或者所认为的“类”,在javascript的世界里,这都是对象,因为你class出来的东西都可以用键值数来描述,这是一种更为本质与微观的看法。

总结

本文的描述内容主要针对以下几方面:

  • js中的对象(具有描述与操作数据的能力)
    • 对象创建
      • 工厂模式
      • 构造函数
  • js中的原型(十分高级的对象)
    • 基本的原型描述
    • 原型链
    • 原型继承

从上述方面依次往下看,我们清晰可以看到javascript语言生命力的不断旺盛。甚至到后来的原型,已经可以实现面向对象中封装、多态、继承的三大概念(本文为了避免大篇幅没有具体展开,其实用多了你就可以感受到的),同时也具有很强大的灵活性。 如果现在你再切回到更宏观更本质的角度来看,基本类型 => 对象 => 原型 => 对象 => 基本类型,是否会觉得javascript的世界清晰了很多呢