js(六)---对象的枚举和继承

对象的枚举

上篇也简单的说到了对象的枚举,接下来就正式介绍一下对象的枚举吧。在正式说说枚举之前,首先要了解一下,对象查找的概念吧。大家都知道对象的查找是obj.name,但是实质上对象的查找的方法是obj[‘name’]。
特别像数组的一种方法,其实数组也是一种特殊的对象,所以说js中一切皆对象,所以说数组用到这个方法也是自然的。

接下来,就说一下吧,说到对象的枚举很自然就想到了for in 操作符。

我们知道要枚举一个数组的所有元素,只要用一个for循环从头到尾遍历一遍就可以了。

但是对象并不能用for循环来遍历属性,所以这里我们就要使用for-in操作了。

var obj = {
      name: 'si',
      age: 18,
      sex: 'male'
}
for(var prop in obj) {
      console.log(prop + ': ' + obj[prop]);//可以把对象的属性给便利一遍
}

我们for-in循环会按照属性的顺序取出属性名然后 赋给prop,所以我们打印的prop都是属性名,obj[prop]则是相对应的属性的值。
attention:这里我们不能写成obj.prop的形式,因为在系统底层会转化成obj[‘prop’]的形式,但是我们并没有prop这个属性,它应该是一个变量,所以会打印出来undefined,这里我们必须使用obj[prop]来调用。

接下来的操作符也挺重要的:

  • hasOwnProperty
    • 这个操作符的作用是查看当前这个属性是不是对象自身的属性,在原型链上的属性则会被过滤掉。如果是自身的,就返回true,否则返回false。
function Person () {
      this.name = 'si'
}
Person.prototype = {
      age:18
}
var oPerson = new Person();
for(var prop in oPerson) {
      if (oPerson.hasOwnProperty(prop)) {
            console.log(oPerson[prop];
      }
}

这个属性,是自己本身的属性不需要原型上的属性,来隔离开来。这样,我们的for-in循环就会只打印自身的name属性而不会去打印原型上的age属性。

  • in操作符
    • 这个操作符的作用是查看一个属性是不是在这个对象或者它的原型里面。
'name' in oPerson; // true
'age' in oPerson; // true
'sex' in oPerson; // false
  • instanceof操作符
    • 这个操作符的作用是查看前面的对象是不是后面的构造函数构造出来的,和constructor挺像的,但是不能全按照constructor来理解。
oPerson instanceof Person; // true
oPerson instanceof Object; // true
{} instanceof Object; // true
{} instanceof Person; //false

后面的构造器的原型是否在前面对象的原型链上。这种解释最完美,也是最完全的。
继承

继承大概有四种方式,这四种方式一种比一种完善。

  • 第一种传统形式
    上文讲的原型的继承:
 Grandfather.prototype.firstName = "si"; 
   function Grandfather(){
        this.money = 1000;
   }
   var grandfather = new Grandfather();
   grandfather.food = "delicious";
   Father.prototype = grandfather;
   function Father(){
        this.age = "19";
   }
   var father = new Father();
   father.name = "si";
   Son.prototype = father;
   function Son(){
       this.sex = "male";
   }
   var son = new Son();
   console.log(son.money)

这种方式有一些缺点,里面有好多的属性用不到,造成了大量的浪费。

  • 第二种借用构造函数
function Father(name, age) {
      this.name = name;
      this.age = age;
}
function Son(name, age) {
      Father.call(this, name, age);
}
var son = new Son('son', 123);
console.log(son.name); // son

这种方式就是利用了call和apply可以改变this指向的特点,通过构造函数来间接的构造子对象。巧妙的把Father中的属性给了Son。

但是这种方式有两个缺点:

1.严格来说,这种方式不属于继承,也访问不了原型的原型。

2.每次构造一个对象都要走两个构造函数,效率很低。

  • 第三种共享原型
Son.prototype = Father.prototype;

这种方式的确挺简单的,但简单指定也会存在其中的不足的。这有一个致命的缺点,就是改变其中一个属性,会导致两个属性都会发生变化,没办法就改一个。

  • 第四种圣杯模式
    • 这种方式是最完善的一种,现在也一直使用。
function inherit (C, P) {
    function F () {}
    F.prototype = P.prototype;
    C.prototype = new F();
}

这里我们利用了一个中间函数F来沟通P和C的原型,这样我们改变C的原型的时候只会影响F而不会影响P,而F这里并没有任何的用处。

function Child () {}
function Parent () {}
Parent.prototype.name = 'si';
inherit(Child, Parent);
console.log(new Child().name); //si

是不是感觉缺少点什么啊?

console.log(new Child().constructor); //function Parent () {}

从上面的代码可以看出,构造函数是Parent,这不是我们想要的,new Child()应该是Child才对的。因为根据上面原型的变换导致把new Child()的原型变成了Parent。只能强制把它改过来。

C.prototype.constructor = C;

同时我们为了保存一下它的父类,也用一个uber来记录一下父类。

C.prototype.uber = P; // 因为super是保留字我们不能使用,所以用了一个uber

形成一个完整的方式:

function inherit(C,P){
function F () {}
      F.prototype = P.prototype;
      C.prototype = new F();
      C.prototype.constructor = C;
      C.prototype.uber = P;
}
console.log(new Child().constructor);// Child

这里面的F函数只是起到一个搭桥的功能,并没有什么实质性的作用,那么我们能不能舍弃掉这个东西呢?

虽然真的舍弃掉是不可能的,但是我们可以通过一定的方法来规避这个函数,让他不出现在我们的继承函数中。

var inherit = (function (){
      var F = function(){};
      return function (C,P){
            F.prototype = P.prototype;
            C.prototype = new F();
            C.prototype.constructor = C;
            C.prototype.uber = P;
      }
}());

这样通过立即执行函数和闭包,我们既可以使用F函数,又可以不让它出现在我们的真正的继承函数中(即返回的那个函数)。这是优化的结果,把在继承中搭桥功能的函数给放在一个闭包,让其释放不出来。建议用这种方式,来做继承。


版权声明:本文为Shine_twinkle原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。