GithubHelp home page GithubHelp logo

blog's People

Watchers

 avatar

blog's Issues

Js 原型链剖析

JS 原型链剖析

原型链是JS的核心语言特性之一。本文要解决的问题有以下几点:

  1. 原型链是为了解决什么问题而出现的?
  2. 原型链有哪些特性?
  3. 如何用原型链实现继承?
  4. 原型链存在哪些问题?(挖坑)

原型链简介

prototype 是为解决js继承问题而提出的一个概念。定义在 prototype 上的属性和方法可以被该类的所有实例共享。
子类的 prototype 指向其父类;原型链的顶端是Object,Object 的 prototype 是 null。

proto 和 prototype

[[prototype]] 是 constructor 的内部属性,用于生成 proto 。如果 prototype 是鸡, proto 就是她下的蛋。当顺着原型链查找属性和方法时其实是从 proto 这条链上查找的。proto 是一个用来访问内部属性 [[prototype]]的 acessor。

function Foo(name) {
  this.name = name;
}
const f = new Foo('b');
f.__proto__ === Foo.prototype

如上

  1. prototype 是构造函数Foo的属性,实例 f 可以通过 proto 来访问。
  2. 构造函数的 proto 是Function,Function 的 proto 是Object。
  3. 构造函数的 prototype 指向他自己,是一个循环引用。

原型链的作用

我们可以通过 prototype 高效地创建类和实现继承。

js 创建类的几种方式

创建对象有多种模式,工厂模式,构造函数模式,原型模式

工厂模式

    function createPerson(name,age){
        const obj = new Object();
        obj.name = name;
        obj.age = age;
        obj.sayName=function(){
            return name;
        }
        return obj;
    }

可以看到,工厂模式创建出来的对象无法判断判断其类型;而且方法不能共享,造成浪费。

构造函数模式

    function Person(name,age){
        this.name=name;
        this.age=age;
        this.sayName=function(){
            return this.name;
        }
    }

通过构造函数模式创建出的对象可以判断类型了,然而方法还是不能共享。

原型模式

    function Person(name,age){
        this.name=name;
        this.age=age;
    }

    Person.prototype.sayName= function(){
        return this.name;
    }

可以看到,通过 prototype 创建的对象既能判断类型,也实现了方法的共享。完美解决了上面两种模式存在的问题。

js实现继承的几种方式

prototype 除了能高效地创建类,也能帮我们实现继承。下面介绍了通过 prototype 实现继承的几种方式。

原型链继承

因为定义在 prototype 上的属性和方法可以被所有实例共享,所以可以通过将父类的实例赋值给子类的 prototype 实现继承。即:
子类的 protoType = new 父类() ,举例:

function Super(){
    this.relation = ['friends','teacher']
}

Super.prototype.saySuperName = function(){
    return this.relation;
}

function Sub(name){
    this.name = name;
}

Sub.prototype = new Super();
Sub.prototype.saySubName = function(){
    return this.subName;
}


const s = new Sub('sub1');
s.saySuperName(); //super
s.relation;  //['friends','teacher']
s.relation.push('student')

const s1 = new Sub('sub2');
s1.relation; //['friends','teacher','student']

可以看到,通过上述方式实现继承有两个严重的问题:如果父类的属性是引用类型的,那么因为所有子类的实例都共用了父类的同一个实例,所以都共用该引用类型的属性,他们其中一个修改了该引用类型的属性,其他实例的该属性都会跟着修改。

借用构造函数

为了解决上述问题,可以在子类的构造函数里面调一下父类的构造函数 Super.call(this,pra1,par2...) ,这样相当于每次创建一个子类的实例时,都复制了一个新的属性给实例。如下:

function Super(relation){
    this.relation = relation;
}

Super.prototype.saySuperName = function(){
    return this.relation;
}

function Sub(name,parentRelation){
    Super.call(this,parentRelation);
    this.name = name;
}

Sub.prototype = new Super();
Sub.prototype.saySubName = function(){
    return this.subName;
}


const s = new Sub('sub1',['friends','parents']);
s.saySuperName(); //super
s.relation;  //['friends','teacher']
s.relation.push('student')

const s1 = new Sub('sub2',['child']);
s1.relation; //['child']

这种方式的问题是:会调用两次父类的构造函数,造成了浪费。一次是在创建子类的时候,一次是在 子类的 protoType = new 父类() 的时候。

道格拉斯方法

子类的 prototype = object(父类.prototype)

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

function Super(relation){
    this.superRelation = ['super0','super1','super2'];
    this.relation = relation;
}

Super.prototype.saySuperName = function(){
    return this.relation;
}

function Sub(name,parentRelation){
    Super.call(this,parentRelation);
    this.name = name;
}

Sub.prototype = object(Super.prototype); 
Sub.prototype.saySubName = function(){
    return this.subName;
}


const s = new Sub('sub1',['friends','parents']);
s.saySuperName(); //super
s.relation;  //['friends','teacher']
s.relation.push('student')

const s1 = new Sub('sub2',['child']);
s1.relation; //['child']

小疑问:Sub.prototype = object(Super.prototype); 这里为啥不直接写成 Sub.prototype = Super.prototype; 呢?因为会破坏父子之间的层级关系。

参考资料

  1. http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
  2. 《js 高级程序设计》第6章-面向对象的程序设计

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.