资讯专栏INFORMATION COLUMN

js回顾:原型链

call_me_R / 2492人阅读

摘要:原型链原型链实际上是中的实现继承的机制,在搞懂原型链之前首先要搞懂几个概念,普通对象和函数对象,构造函数,对于很多人来说是混杂不清的概念,但是想要弄清楚原型链我们必须了解什么是首先,只能在存在与函数中其次,其实是当前函数所在上下文环境,再简

原型链

原型链实际上是JavaScript中的实现继承的机制,在搞懂原型链之前首先要搞懂几个概念:this,普通对象和函数对象,构造函数,new

this

this对于很多人来说是混杂不清的概念,但是想要弄清楚原型链我们必须了解什么是this

首先,this只能在存在与函数中

其次,this其实是当前函数所在上下文环境,再简单一点也可以理解为this返回一个当前函数所在的对象,也就是说想要知道this是什么我们只需要关注是谁调用了this所在的函数就可以了

如下边的代码zhao.sayName()是zhao调用的sayName函数所以sayName中的this自然指的就是zhao这个对象,而下方 var liName = zhao.sayName语句是将sayName这个函数赋值给liName,调用liName就相当于在最顶层也就是window下直接调用sayName,this的指向自然就是window这个最顶层对象

    var name = "Li"
    var zhao = {
        name: "Zhao",
        sayName: function () {
            console.log(this.name);
        }
    }
    zhao.sayName() // Zhao
    var liName = zhao.sayName;
    liName() // Li
普通对象与函数对象

JavaScript中一切都可以看作对象,但是实际上对象也是有区别的,对象分为普通对象函数对象

    // 普通对象
    var o1 = {}
    var o2 = new Object()
    var o3 = new f1()
    // 函数对象
    function f1(){}
    var f2 = function(){}
    var f3 = new Function()

    console.log(typeof f1); //function
    console.log(f1.prototype); //true
    console.log(typeof f2); //function
    console.log(f2.prototype); //true
    console.log(typeof f3); //function
    console.log(f3.prototype); //true

    console.log(typeof o1); //object
    console.log(o1.prototype); //undefined
    console.log(typeof o2); //object
    console.log(o2.prototype); //undefined
    console.log(typeof o3); //object
    console.log(o3.prototype); //undefined

凡是通过function构建的对象都是函数对象,并且只有函数对象才有prototype属性,普通对象没有

prototype 原型

prototype又是什么呢?

当我们创建函数的时候,编译器会自动为该函数创建一个prototype属性,这和属性指向一个包含constructor属性的对象,而这个属性又默认指回原函数,读起来有点绕对吧,大概是这样的

    function Person() {
    //    prototype = {
    //        constructor: Person,
    //    }
    }

每个函数对象都有一个prototype(原型)属性,在我看来prototype属性的意义:

创建对象的模板

公开的共享空间

这两点等学习了下边new命令你就会明白了

constructor 构造函数

函数对象的一种用法就是构造函数,通过构造函数可以构建一个函数对象的实例(普通对象)

    function Person(name, age ){
        this.name = name;
        this.age = age;
        this.sayHello = function(){
            console.log(`Hello! my name is ${this.name}`);
        };
    }

    var person1 = new Person("kidder", 28);
    person1.sayHello(); // Hello! my name is kidder
    console.log(person1.constructor); //[Function:Person]

按照惯例,构造函数的命名以大写字母开头,非构造函数以小写字母开头,通过构造函数构造的普通对象都会有一个constructor(构造函数)属性,该属性指向构造该对象的构造函数

new命令 new命令的工作机制

创建一个空对象作为要返回对象的实例

将这个空对象的原型(__ proto __)指向构造函数的prototype属性

将这个空对象赋值给构造函数内部的this

执行构造函数内部的代码

原型链

下面我们来看看构造函数构建一个普通对象的时候发生了什么

    var Person = function (name) {
        this.name = name;
        this.age = 18;
    };
    Person.prototype.sayHello = function(){
        console.log(`Hello! my name is ${this.name}`);
    };
    var li = new Person("Li");
    console.log(li.name); // Li
    console.log(li.age); // 18
    li.sayHello(); // Hello! my name is Li

创建一个空对象作为要返回对象的实例

    {}

将这个空对象的原型(__ proto __)指向构造函数的prototype属性

    {
        __proto__:Person.prototype;
    }

将这个空对象赋值给构造函数内部的this

    this = {
        __proto__:Person.prototype;
    }

执行构造函数内部的代码

   this = {
        __proto__:Person.prototype;
       name: "Li";
       age: 18;
   }

所以li这个对象中只有name和age两个属性,为什么li.sayHello()会输出Hello! my name is Li呢?

这就是原型链,当给定的属性在当前对象中找不到的情况下,会沿着__proto__这个属性一直向对象的上游去寻找,直到__proto__这个属性指向null为止,如果找到指定属性,查找就会被截断,停止

上面这张图是整个JavaScript的原型链体系,为了让这张图更直观所以我将构造函数的prototype属性多带带提了出来,恩,其实画在构造函数内部也可,但同时因为对象是引用类型,所以这样画也没毛病吧

_ proto _ 和 prototype

这两个属性经常会被我们混淆,那么我们回过头再来总结一下

prototype:只有函数对象才具有的属性,它用来存放的是构造函数希望构建的实例具有的共享的属性和方法,主要用于构造函数的实例化

_proto_ : 所有对象都具有的属性,它指向的是当前对象在原型链上的上级对象,主要作用是让编译器在由__proto__这个属性构成的原型链上查找特定的属性和方法

补充 prototype的共享属性
    var Person = function (name) {
        this.name = name;
        this.age = 18;
    };
    Person.prototype.sayHello = function(){
        console.log(`Hello! my name is ${this.name}`);
    };
    var li = new Person("Li");

    var Person1 = function () {
    };
    Person.prototype.name = "Li"
    Person.prototype.age = 18
    Person.prototype.sayHello = function(){
        console.log(`Hello! my name is ${this.name}`);
    };

    var Li = new Person1();

关于Person和Person1两种构造函数的写法有什么不同呢?

一般来说写在prototype原型对象中的属性和方法都是公用的,也就是说写在构造函数中的属性在构建普通对象的时候,都会在新对象中重新定义,也就是从内存的角度来说又会多占用一些内存空间,所以我们将构造函数的所有属性和方法都写在prototype原型中不好吗?

但是原型函数也是有缺点的:

不够灵活

    var Person = function () {
    };
    Person.prototype.name = "Li"
    Person.prototype.age = 18
    Person.prototype.sayHello = function(){
        console.log(`Hello! my name is ${this.name}`);
    };

    var li = new Person();
    var zhao = new Person();

这种方式构造的所有对象都是一个模板,虽然我们也可以在当前对象下进行修改,但这样一点也不优雅,不规整,而且从某种意义上来说也是对内存的浪费

对于引用类型的修改会被全部共享

    var Person = function () {
    };
    Person.prototype.name = "Li"
    Person.prototype.age = 18
    Person.prototype.friends = ["ZhangSan", "LiSi"]
    Person.prototype.sayHello = function(){
        console.log(`Hello! my name is ${this.name}`);
    };

    var li = new Person();
    var zhao = new Person();
     li.friends.push("WangWu");
    console.log(zhao.friends); // [ "ZhangSan", "LiSi", "WangWu" ]

在JavaScript中,基本类型的修改可以明确的通过创建或修改在当前对象下的属性对原型链进行截断,但是像数组,对象这种引用类型的值虽然也可以通过在当前对象中创建该属性来对原型链进行截断,但是一不注意就可能会出现上面这种情况直接对原型进行了修改

构造函数与原型相结合

所以,用构造函数来定义实例属性,用原型定义方法和共享的属性,这样写就比较优雅了

    function Person(name, age){
        this.name = name;
        this.age = age;
        this.friends = ["ZhangSan", "LiSi"];
    }
    Person.prototype.sayHello = function(){
        console.log(`Hello! my name is ${this.name},${this.age}岁了`);
    };
    var li = new Person("li", 18);
    var zhao = new Person("zhao", 16);
    li.sayHello();
    // Hello! my name is li, 18岁了
    zhao.sayHello();
    // Hello! my name is zhao,16岁了
    li.friends.push("WangWu");
    console.log(zhao.friends);
    // [ "ZhangSan", "LiSi" ]
创建对象的几种方式

构造函数方式

法一用构造函数构造一个新对象

    var A = function () { };
    var a = new A();
    console.log(a.constructor); // [Function:A]
    console.log(a.__proto__ === A.prototype); //true

字面量方式

法二的本质来说和法一是一样的,就是隐式调用原生构造函数Object来构造新对象

    var a = {};
    // var a = new Object();
    console.log(a.constructor); // [Function:Object]
    console.log(a.__proto__ === Object.prototype); //true

create方式

法三Object.create是以一个普通对象为模板创建一个新对象

    var a1 = {a:1}
    var a2 = Object.create(a1);
    console.log(a2.constructor); // [Function:Object]
    console.log(a2.__proto__ === a1);// true
    console.log(a2.__proto__ === a1.prototype); //false

所以除了Object.create创建对象的方式,可以说:__ proto __ === constructor.prototype;

constructor

前面我们说道prototype的时候进行原型属性的赋值的时候,采用的是逐项赋值,那么当我直接将对象赋值给prototype属性的时候会发生什么呢?

    function Person() { }
    Person.prototype = {
        name : "Li",
        age : 18,
        sayHello : function () {
            console.log(`Hello! my name is ${this.name},${this.age}岁了`);
        }
    };
    var li = new Person();
    console.log(li instanceof Object);        // true
    console.log(li instanceof Person);        // true
    console.log(li.constructor === Person);    // false
    console.log(li.constructor === Object);    // true
    console.log(Person.prototype.constructor);  // Object

这时候我们就发现我们构建的li对象的constructor不再指向它的构造函数Person,而是指向了Object,并且Person原型Person.prototype的constructor指向也指向了Object,这是什么原因呢?

其实,根源出现在Person.prototype上,上边我们提到过,其实我们在写构造函数的时候实际上是这样的

    function Person() {
    // prototype = {
    //    constructor : Person
    // }
    }

当我们构建Person构造函数的时候,编译器会自动生成一个带有指向Person的constructor属性的对象,并把这个对象赋值给Person.prototype,我们又知道js中对象是引用类型,当我们使用Person.prototype.name=...的时候实际上是对这个对象的修改,而使用Person.prototype={...}实际上是将这个属性原本的指针指向了另一个新创建的对象而不是原来编译器自动创建的那个:

而li的constructor属性自然是继承自Person.prototype,所以constructor自然也就跟着改变了,如果在编程的过程中constructor这个属性很重要的话可以通过下面的方式

    function Person() { }
    Person.prototype = {
        constructor:Person
        name : "Li",
        age : 18,
        sayHello : function () {
            console.log(`Hello! my name is ${this.name},${this.age}岁了`);
        }
    };
    
    var li = new Person();
    console.log(li instanceof Object);        // true
    console.log(li instanceof Person);        // true
    console.log(li.constructor === Person);    // true
    console.log(li.constructor === Object);    // false
    console.log(Person.prototype.constructor);  // Person
结语:

参考:《JavaScript高级程序设计》

这是我对JS原型链部分的总结与思考,也是我写的第一篇这么正式的技术文档,如有纰漏之处,欢迎大家批评指正

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102330.html

相关文章

  • 关于javascript的原型原型,看我就够了(一)

    摘要:要用作原型的对象。函数对象可以创建普通对象,这个我们上面讲过了回顾一下这是一个自定义构造函数普通对象没法创建函数对象,凡是通过创建的对象都是函数对象,其他都是普通对象通常通过创建,可以通过来判断。 关于js的原型和原型链,有人觉得这是很头疼的一块知识点,其实不然,它很基础,不信,往下看要了解原型和原型链,我们得先从对象说起 创建对象 创建对象的三种方式: 对象直接量 通过对象直接量创建...

    MoAir 评论0 收藏0
  • 前端面试回顾(1)---javascript的面向对象

    摘要:每个类有三部分构成第一部分是构造函数内,供实例对象化复制用。第二部分是构造函数外,直接通过点语法添加,供类使用,实例化对象访问不到。组合继承还有一个要注意的地方在代码处,将子类原型的属性指向子类的构造函数。 前言 前一阵面试,过程中发现问到一些很基础的问题时候,自己并不能很流畅的回答出来。或者遇到一些基础知识的应用,由于对这些点理解的不是很深入,拿着笔居然什么都写不出来,于是有了回顾一...

    animabear 评论0 收藏0
  • JS面向对象的程序设计之继承的实现 - 原型

    摘要:简单回顾一下构造函数原型和实例对象之间的关系每个构造函数都有一个原型对象。找到生成构造函数的原型对象的构造函数,搜索其原型对象,找到了。 JS面向对象的程序设计之继承的实现 - 原型链 前言:最近在细读Javascript高级程序设计,对于我而言,中文版,书中很多地方翻译的差强人意,所以用自己所理解的,尝试解读下。如有纰漏或错误,会非常感谢您的指出。文中绝大部分内容引用自《JavaS...

    zhaochunqi 评论0 收藏0
  • js温故而知新7(面向对象编程)——学习廖雪峰的js教程

    摘要:不区分类和实例的概念,而是通过原型来实现面向对象编程。新创建的的原型链是也就是说,的原型指向函数的原型。最后,创建一个对象代码和前面章节完全一样小明继承用定义对象的另一个巨大的好处是继承更方便了。 JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。 原型是指当我们想要创建xiaoming这个具体的学生时,我们并没有一个Student类型可用...

    Jaden 评论0 收藏0
  • 关于原型中constructor、prototype及__proto__的问题

    摘要:最近看到一个关于原型链的问题,回顾一下原型链的知识点。说说为什么为什么是。首先不是自身的属性,而是原型链上的,即的原型中。类似通过这样来找到的值。,不是复制了对象,而是把指向了,所以对的修改会影响到的值。再看看这张图一切都明朗了。 最近看到一个关于原型链的问题,回顾一下原型链的知识点。 function person(name) { this.name = name; ...

    alanoddsoff 评论0 收藏0

发表评论

0条评论

call_me_R

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<