套公式让你不再害怕JavaScript中的原型链

javascript/jquery

浏览数:63

2019-3-9


内容大纲.png

前言

通过上一篇套公式让你不再害怕JavaScript中的作用域基本熟悉了JavaScript中的作用域链,然而JavaScript中还有一条比较重要的链,叫原型链.JavaScript让很多人觉得很诡异,但是一旦弄清楚原理,自然一通百通.

typeof obj 和 obj instanceof Type

在JavaScript中,我们经常用typeof objobj instanceof Type来识别类型,那么两者的区别在哪?先来看两段代码

<!--typeof obj的方式判断-->
<script>
    
    var str = "toby";
    console.log(typeof str);// string
    
    var num = 1;
    console.log(typeof num);// number
    
    var str2 = new String("toby");
    console.log(typeof str2);// object
    
    var num2 = new Number(1);
    console.log(typeof num2);// object
</script>
<!--obj1 instanceof obj2的方式判断-->
<script>

    var str = "toby";
    console.log(str instanceof String); // false
    
    var num = 1;
    console.log(num instanceof Number); // false
    
    var str2 = new String("toby");
    console.log(str2 instanceof String); // true
    
    var num2 = new Number(1);
    console.log(num2 instanceof Number); // true
</script>

这次就不卖关子,直接把输出结果和结论告诉大家,从这两段代码我们就能得出结论

  • typeof obj: 返回obj对象的类型的全小写的字符串,只针对基本类型有效,如果作用在引用类型上,都返回object字符串,无法判断变量的真实类型

  • obj instanceof Type : 判断obj是否属于Type类型,返回boolean,只能作用在引用类型上,如果作用在基本类型上,都是返回false的结果.

但是知其然必须知其所以然,我们接下来进一步探索

JavaScript中的原型链

JavaScript中的类

JavaScript中是没有类的概念,创建对象是用构造函数来完成,或者直接用json格式{}来写对象

<script>
    //创建一个Person类,首字母要大写,这个function也是这个类的构造方法
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    var p = new Person("toby", 24);
    console.log(p); 
</script>

这个new的过程到底发生了什么,我们看一下文档的描述

When the code new Foo(…) is executed, the following things happen:

1.A new object is created, inheriting from Foo.prototype.

2.The constructor function Foo is called with the specified arguments, and with this bound to the newly created object. new Foo is equivalent to new Foo(), i.e. if no argument list is specified, Foo is called without arguments.

3.The object returned by the constructor function becomes the result of the whole new expression. If the constructor function doesn’t explicitly return an object, the object created in step 1 is used instead. (Normally constructors don’t return a value, but they can choose to do so if they want to override the normal object creation process.)

我们用代码描述一下new Person("toby", 24);

//1.方法new的瞬间,得到一个空对象{},它继承自Person.prototype
var p = {};
p.__proto__ = Person.prototype;
//2.方法的this,指向空对象
//3.运行构造方法
{}.name = toby;
{}.age = age;
//4.返回该对象
Person.call(p); 

JavaScript对象中的内部结构

1.__proto__: 这个属性是JS对象上的隐藏属性,这个属性指向的是该对象对应类型的prototype对象

var p = new Person();
console.log(p.__proto__ == Person.prototype);//true

2.js中对象分为两部分,

原生和拓展.png

一部分是原生部分,原生部分是在构造函数中定义的,或者是直接通过对象.属性=value赋值的;

一部分是扩展部分,扩展部分的内容是通过类.prototype=value赋值的;

JavaScript中的两种继承方式

普通继承

<script>
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.sayName = function () {
        console.log(this.name);
    };
    //创建一个Student类
    function Student(name, age, sn) {
        //调用父类的构造方法帮我们初始化属性
        Person.apply(this, [name, age]);
        this.sn = sn;
    }
    //复制Person原型中的所有方法
    for (var p in Person.prototype) {
        Student.prototype[p] = Person.prototype[p];
    }

    var s = new Student("toby", 24, 1);
    s.sayName();
    console.log(s instanceof Student);
    console.log(s instanceof Object);
    console.log(s instanceof Person);
</script>

又到了开心做题的时间了,根据开头的结论,请问这里会输出什么?可能我们认为输出为

  • toby
  • true
  • true
  • true

但是输出后发现,结果却为

  • toby
  • true
  • true
  • false

如果倒数第二个为false可能你还比较好理解,最后一个为false似乎和我们观念中的继承有太大冲突,那么,我们来看原理

instanceof的原理

普通继承.png

obj instanceof Type的原理:沿着对象的原型链,判断一条原型链中是否有一个原型和Type的类型一样,如果找到,返回true,如果一直沿着原型链找下去都找不到,则返回false,那么我们就把这个当成一个公式,套上公式走一波

console.log(s.__proto__ == Student.prototype);//true
console.log(s.__proto__ == Object.prototype);//false
console.log(s.__proto__.__proto__ == Object.prototype);//true
console.log(s.__proto__ == Person.prototype);//false
console.log(s.__proto__.__proto__ == Person.prototype);//false

所以现在明白为什么前面两个输出为true,最后一个输出为false了吧.

但是如果就想让s instanceof Person结果为true,达到子类对象能被识别成父类类型实例的需求,那么怎么办呢?既然原理我们都知道了,那解决起来是很简单的

原型继承

<script>
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.sayName = function () {
        console.log(this.name);
    };
    function Student(name, age, sn) {
        Person.apply(this, [name, age]);
        this.sn = sn;
    }

    //在Student原型中添加Person原型
    Student.prototype = Object.create(Person.prototype);

    var s = new Student("toby", 24, 1);
    console.log(s);

    console.log(s instanceof Student);
    console.log(s instanceof Object);
    console.log(s instanceof Person);
</script>

那最后输出的结果究竟是不是我们想要的呢?我们分析照着公式分析一波

原型继承.png

console.log(s.__proto__ == Student.prototype);//true
console.log(s.__proto__ == Object.prototype);//false
console.log(s.__proto__.__proto__ == Object.prototype);//false
console.log(s.__proto__.__proto__.__proto__ == Object.prototype);//true
console.log(s.__proto__ == Person.prototype);//false
console.log(s.__proto__.__proto__ == Person.prototype);//true

从这里我们就知道,输出的结果为

  • true
  • true
  • true

原型链的分析

那么,我们用两个图来描述一下这两种继承中的原型链

原型链.png

关于肥朝

有段时间我总是在思考,为什么周末的时候早上醒过来的时候,总是已经10点多,然而工作日的时候,醒过来的时候,却是7点多?这个看似很傻逼的问题,但是却反映出一个问题,那就是人的潜意识

当工作日的时候,你潜意识告诉你要早起, 你知道你还有工作要做,所以你自然就能早起,而休息日的时候,你的潜意识告诉你可以睡懒觉了,你可能就是直接”昏迷”到11点之后了.这就是很多人一直不愿意离开自己的语言舒适区的原因,因为他们潜意识告诉他们,这是前端的东西,我是做后端的,没必要去了解他们,或者说这是后端的东西,我前端的没必要去接触.

所以我觉得,要成长,做的第一步就是离开自己的舒适区,不要害怕困难,你遇到的困难越大,说明你能力就越大,一个人很少会遇到超出自身能力好几倍的困难,比如没有谁遇到了怎么拯救世界的这种困难吧.