5

理解JavaScript的原型链和继承

 3 years ago
source link: https://blog.oyanglul.us/javascript/understand-prototype
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
理解JavaScript的原型链和继承

理解JavaScript的原型链和继承

Table of Contents

instanceof 引发的问题

instanceof 运算符可以用来判断某个构造函数的prototype属性是否存在另外一个要检测对象的原型链上 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof

什么意思呢?

Function instanceof Object;

用高中数学的话就是把x,y代入公式得:

instanceof 运算符可以用来判断Object的 prototype属性 是否存在Function的 原型链 上。

等等,斜体字的这俩到底是什么鬼意思?

prototype属性是原型链吗?

JS是基于原型链面向对象语言,也就是说所有对象都是以对象为模板创建实例的。如果是其他oo语言的背景比如Java或Ruby,都习惯于创建一个class模板,class创建object实例。比如ruby:

  class A
    def initialize name
      @name = name
    end
    def to_s
      @name
    end
  end
puts A.new('hehe') # => hehe

这里的类A就是所有 A.new 创建出来的实例的模板而已。而对于原型链语言JS来说,同意的事情要这样做

function A(name){
  this.name = name
}
A.prototype.toString = function(){
  return this.name
}
var a = new A('hehe')
console.log('object name is:' + new A ('hehe')) // => object name is: hehe
  1. 这里的怪怪的函数其实就是constructor,相当于ruby例子里的initialize
  2. 而prototype上的方法toString也就是类似class模板上的方法。

为什么要把方法绑到prototype上?直接 A.toString…= 不行吗?

在解释prototype之前,先解释一下 new A 到底发生了什么 这里只是意思,但是如果真的改变 __proto__ 是非常低效的 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto

1: // var a = new A('hehe') =>
2: var a = new Object();
3: a.__proto__ = A.prototype; (proto)
4: A.call(a, 'hehe');

其中 A.call 的意思是先把A的this设置为a,然后执行A的body也就是 this.name=name

但是 __proto__ 又是什么

__proto__ 才是原型链

__proto__ 是内部 [ [Prototype ]] (说了半天原型链这就是牛逼闪闪的 原型链, 指向对象或者null)的getter和setter方法(已加入ES6规范 http://www.ecma-international.org/ecma-262/6.0/#sec-additional-properties-of-the-object.prototype-object ,但是还是建议只使用Object.getPrototypeOf())

JS对象能使用它原型链对象的所有方法,比如所有的对象的原型链(的原型链的原型链的原型链…)都最终会指向Object(或null)。因此,所有的对象都能使用Object.prototype上的方法,比如我之前覆盖掉的 toString 本身就是Object.prototype上的方法,如果没有覆盖,它是可以拿到所有Object上的方法的:

a.toString === A.prototype.toString // true
a.toLocalString === Object.prototype.toLocalString // true
a.__proto__ === A.prototype // true

所以,现在是否可以理解这句话了呢

instanceof 运算符可以用来判断Object的 prototype属性 是否存在Function的 原型链 上。

所以instanceof其实就是

Function.__proto__ === Object.prototype
// false

擦,假设失败了呢,让我们来看看为什么不对,Function.__proto__到底指哪去了

Function.__proto__ === Function.prototype
//true

原来指向自己的prototype了呢,那就意味着…

Function instanceof Function
//true

yes,然而 Function instanceof Object似乎也能解释了

Function.__proto__ === Function.prototype
Function.__proto__.__proto__ === Object.prototype

所以如果我们让

Function.__proto__.__proto__ = null
Function instanceof Object
//false

这回知道为什么不要用 __proto__ 了吧,一不小心重写了会导致所有继承自它的对象都受影响。

为了养成良好的习惯,实际项目最好使用 getPrototypeOf 取原型链,这里只是为了方便我采用__proto__

下面来看第二个题

Object instanceof Function

难道可以互相链吗?这意味着

Object.__proto__ === Function.prototype
// true
// 但是Firefox取不到Object.__proto__, 看来做了保护,必须要用
// Object.getPrototypeOf(Object) === Function.prototype

要晕了, 忍不住要画个图

image.png

多简单呢,一共就分别有两类:

  • 原型链指向Function.prototype的函数们
  • 原型链指向Object.propotype的对象们

而原型链顶端的Object.prototype就再没有原型链了,所以是空

现在再回头看题目是不是so easy了。

也没什么卵用得 contructor

如果你好奇的在FireFox Console中看一下 a 除了刚才那些玩意,还有一个奇怪的东西

image.png

话说 A 里面这个constructor是个什么鬼,我们来玩它一下

a.constructor === A.prototype.constructor
A.prototype.constructor === A
A.prototype.constructor = null
a.constructor // => null
a instanceof A // true

这只是函数都有的一个玩意而已, 由于js的函数可以作为构造器,也就是可以 new ,所以所有的 函数的prototype.constructor都指向自己,因此所有的 new 出来的对象也都有一个reference能找到自己的构造器。

然而除了这个功能也并没有什么卵用嘛。

真的是这样吗?

恩,真的!

Bonus 继承

下面这个是babel 从es6 class

class A{
  constructor(name) {
    this.name= name
  }
  toString() {
    return this.name
  }
}

class B extends A {
  toString(){
    return this.name + 'b'
  }
}

编译出来的ES5继承

function _inherits(subClass, superClass) { 
    // 密
}

var A = (function () {
    function A(name) {
        this.name = name;
    }

    A.prototype.toString = function toString() {
        return this.name;
    };

    return A;
})();

var B = (function (_A) {
    function B() {
        if (_A != null) {
            _A.apply(this, arguments);
        }
    }

    _inherits(B, _A);

    B.prototype.toString = function toString() {
        return this.name + 'b';
    };

    return B;
})(A);

其他地方都不用看了,inherits 函数用到了之前学到的所有玩意,要求实现要满足下列所有的cases,就当是课后练习了:

var a= new A('A');
var b= new B('B');
a.constructor === A &&
b.constructor === B &&
a instanceof A &&
b instanceof A &&
b instanceof B

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK