资讯专栏INFORMATION COLUMN

JS核心知识点梳理——原型、继承(上)

villainhr / 3401人阅读

摘要:同理,原型链也是实现继承的主要方式的只是语法糖。原型对象也可能拥有原型,并从中继承方法和属性,一层一层以此类推。利用构造函数小明张三张三小明缺点每次实例化都需要复制一遍函数到实例里面。寄生构造函数模式只有被类出来的才能用。

引言

最近又攀登了一下JS三座大山中的第二座。登山过程很酸爽,一路发现了许多之前没曾注意到的美景。本着独乐乐不如众乐乐的原则,这里和大家分享一下。

JS的面试对象
有些人认为 JavaScript 不是真正的面向对象的语言,比如它没有像许多面向对象的语言一样有用于创建class类的声明(在 ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的)。JavaScript 用一种称为构建函数的特殊函数来定义对象和它们的特征。

不像“经典”的面向对象的语言,从构建函数创建的新实例的特征并非全盘复制,而是通过一个叫做原形链的参考链链接过去的。同理,原型链也是实现继承的主要方式(ES6的extends只是语法糖)。

原型、原型链

一直在犹豫,到底是先讲创建对象的方法还是先讲原型。为了后面保证讲创建对象方法的连贯性,这里还是先讲讲原型吧,
这里为了权威,直接就摘抄MDN的定义了

JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身。

这个__proto__属性有什么用呢?在传统的 OOP 中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制,而是在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。

简单的说,就是实例对象能通过自己的__proto__属性去访问“类”原型(prototype)上的方法和属性,类如果也是个实例,就会不断往上层类的原型去访问,直到找到

补充:
1.“类”的原型有一个属性叫做constructor指向“类”
2.__proto__已被弃用,提倡使用Object.getPrototypeOf(obj)

举例:

var arr = [1,2,3] //arr是一个实例对象(数组类Array的实例)
arr.__proto__ === Array.prototype //true  实例上都有一个__proto__属性,指向“类”的原型
Array.prototype.__proto__ === Object.prototype //true “类”的原型也是一个Object实例,那么就一定有一个__proto__属性,指向“类”object的原型

这里补充一个知识点:
浏览器在在Array.prototype上内置了pop方法,在Object.prototype上内置了toString方法


上图是我画的一个原型链图

[1,2,3].pop() //3
[1,2,3].toString() //"1,2,3"
[1,2,3].constructor.name //"Array" 
[1,2,3].hehe() //[1,2,3].hehe is not a function

当我们调用pop()的时候,在实例[1,2,3]上面没有找到该方法,则沿着原型链搜索"类"Array的原型,找到了pop方法并执行,同理调用toString方法的时候,在"类"Array没有找到则会继续沿原型链向上搜索"类"Object的原型,找到toString并执行。
当执行hehe方法的时候,由于“类”Object的原型上并没有找到,搜索“类”Object的__proto__,由于执行null,停止搜索,报错。

注意,[1,2,3].constructor.name显示‘Array’不是说明实例上有constructor属性,而是正是因为实例上没有,所以搜索到的原型上了,找到了constructor

类,创建对象的方法

怎么创建对象,或者说怎么模拟类。这里我就不学高程一样,给大家介绍7种方法了,只讲我觉得必须掌握的。毕竟都es6 es7了,很多方法基本都用不到,有兴趣自己看高程。

利用构造函数
 const Person = function (name) {
        this.name = name
        this.sayHi = function () {
            alert(this.name)
        }
    }
    const xm = new Person("小明")
    const zs = new Person("张三")
    zs.sayHi() //"张三"
    xm.sayHi() //"小明"

缺点: 每次实例化都需要复制一遍函数到实例里面。但是不管是哪个实例,实际上sayHi都是相同的方法,没必要每次实例化的时候都复制一遍,增加额外开销。

组合使用原型和构造函数
    //共有方法挂到原型上
    const Person = function () {
         this.name = name
    }
    Person.prototype.sayHi = function () {
            alert(this.name)
        }
    const xm = new Person("小明")
    const zs = new Person("张三")
    zs.sayHi() //"张三"
    xm.sayHi() //"小明"

缺点:基本没啥缺点了,创建自定义类最常见的方法,动态原型模式也只是在这种混合模式下加了层封装,写到了一个函数里面,好看一点,对提高性能并没有卵用。

es6的类

es6的‘类’class其实就是语法糖

class Person {
   constructor(name) {
       this.name = name
  }
  say() {
       alert(this.name)
  }
}
const xm = new Person("小明")
const zs = new Person("张三")
zs.sayHi() //"张三"
xm.sayHi() //"小明"

在es2015-loose模式下用bable看一下编译

"use strict";

var Person =
/*#__PURE__*/
function () {
  function Person(name) {
    this.name = name;
  }

  var _proto = Person.prototype;

  _proto.say = function say() {
    alert(this.name);
  };

  return Person;
}();

分析:严格模式,高级单例模式封装了一个类,实质就是组合使用原型和构造函数

寄生构造函数模式

比如现在需要创建一些特殊的数组,这些数组有sayContent方法,可以打印出自己的内容。怎么创建出这个特殊的数组类,又不影响Array类

function specialArray() {
        var arr = new Array(...arguments)
        arr.sayContent = function () {
             console.log([...this.values()])
        }
        return arr
    }
    var arr = new specialArray("x","y","z")

这个和在数组的原型链上增加方法有啥区别?原型链上增加方法,所有数组都可以用。寄生构造函数模式只有被specialArray类new出来的才能用。

JS世界里的关系图

知识点:

Object.getPrototypeOf(Function) === Function.prototype // Function是Function的实例,没毛病

Object.getPrototypeOf(Object.prototype)

任何方法上都有prototype属性以及__proto__属性
任何对象上都有__proto__属性

Function.__proto__.__proto__===Object.prototype

Object.getPrototypeOf(Object)===Function.prototype

最高级应该就是Function.prototype了,因为5

判断类型的方法

之前在JS核心知识点梳理——数据篇里面说了一下判断判断类型的四种方法,这里借着原型再来分析一下

1. typeof:

只能判断基础类型中的非Null,不能判断引用数据类型(因为全部为object)它是操作符

2. instanceof:

用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置 风险的话有两个

//判断不唯一
[1,2,3] instanceof Array //true
[1,2,3] instanceof Object //true
//原型链可以被改写
const a = [1,2,3]
a.__proto__ = null
a instanceof Array //false

仿写一个instanceof,并且挂在Object.prototype上,让所有对象都能用

//仿写一个instance方法
    Object.prototype.instanceof = function (obj) {
        let curproto = this.__proto__
        while (!Object.is(curproto , null)){
            if(curproto === obj.prototype){
                return true
            }
            curproto = curproto.__proto__
        }
        return false
    }
   
[1,2,3].instanceof(Array) //true
[1,2,3].instanceof(Object) //true
[1,2,3].instanceof(Number) //false
[1,2,3].instanceof(Function) //false
1..instanceof(Function) //false
(1).instanceof(Number) //true
3. constructor:

constructor 这玩意已经介绍过了,“类”的原型执行constructor指向“类”
风险的话也是来自原型的改写

[1,2,3].constructor.name //"Array"

// 注意下面两种写法区别
Person.protorype.xxx = function //为原型添加方法,默认constructor还是在原型里
Person.protorype = { //原型都被覆盖了,没有constructor了,所要要手动添加,要不然constructor判断失效
   xxx:function
   constructor:Person
}
4.Object.prototype.toString.call(xxx)

试了下,好像这个方法也不是很准
null 可以用object.is(xxx,null)代替
Array 可以用Array.isArray(xxx)代替

Object.prototype.toString.call([1,2,3])   //"[object Array]"
Object.prototype.toString.call(function(){}) //"[object Function]"
Object.prototype.toString.call(1) //"[object Number]"
Object.prototype.toString.call(null) //"[object Null]"
Object.prototype.toString.call({}) //"[object Object]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(true) // 特别注意 特别注意 特别注意"[object Object]"
Object.prototype.toString.call("string") //  特别注意 特别注意 特别注意 "[object Undefined]" 
in操作符

对于for in 和in 都是沿着原型链查找属性是否存在,可以利用hasOwnProperty进行相关过滤

// "in" operation test
class Person {
        constructor (name) {
            this.name = name
        }
        sayHi() {
            console.log("Hi")
        }
    }
    var p1 = new Person("小明")


"name" in p1 //true
"sayHi" in p1 //true 

for (var i in p1) {
        if (p1.hasOwnProperty(i)) {
            console.log("ownProperty:" + i)
        } else {
            console.log("prototypeProperty: " + i)
        }
    }
//"ownProperty: name"
// prototypeProperty: sayHi
总结

参照各种资料,结合自己的理解,在尽量不涉及到继承的情况下,详细介绍了原型及其衍生应用。由于本人技术有限,如果有说得不对的地方,希望在评论区留言。

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

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

相关文章

  • JS核心识点梳理——原型继承(下)

    摘要:引言上篇文章介绍原型,这篇文章接着讲继承,呕心沥血之作,大哥们点个赞呀明确一点并不是真正的面向对象语言,没有真正的类,所以我们也没有类继承实现继承有且仅有两种方式,和原型链在介绍继承前我们先介绍下其他概念函数的三种角色一个函数,有三种角色。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 上篇文章介绍原型,...

    joyqi 评论0 收藏0
  • js知识梳理4.继承的模式探究

    摘要:而且在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。在主要考虑对象而不是自定义类型和构造函数的情况下,这个模式也不错。 写在前面 注:这个系列是本人对js知识的一些梳理,其中不少内容来自书籍:Javascript高级程序设计第三版和JavaScript权威指南第六版,感谢它们的作者和译者。有发现什么问题的,欢迎留言指出。 1.原型链 将原型链作...

    wenshi11019 评论0 收藏0
  • JS核心识点梳理——变量篇

    摘要:核心知识点梳理数据篇看了一些资料,结合高程和对核心知识点进行了梳理。所以,一共有种声明变量的方法。凡是在声明之前就使用这些变量,就会报错。还是那句话,建议大家掌握核心知识点,细枝末节的东西就随意啦。 JS核心知识点梳理——数据篇 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 看了一些资料,结合ES6、高程和MD...

    aristark 评论0 收藏0
  • JS核心识点梳理——下文、作用域、闭包、this(中)

    摘要:引言满满的干货,面试必系列,参考大量资料,并集合自己的理解以及相关的面试题,对核心知识点中的作用域闭包上下文进行了梳理。本篇重点介绍闭包和。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 满满的干货,面试必bei系列,参考大量资料,并集...

    rottengeek 评论0 收藏0
  • JS开发中函数识点梳理(二)

    摘要:英文原文中本来是,而翻译成第一类公民其实就是一种比喻。所以,通过上述的结果,我们发现在中不管我们是用构造函数创建的对象还是用本身提供的数据类型创建的对象都源自于。使用可以解除函数体内代码和函数名的耦合状态。 作为一个Jser,不光要会用js,还要明白它的运行原理,不然就会一直停留在表面。 函数在JavaScript中被称作第一等公民,这个第一等公民是什么鬼?看看知乎上是怎么回答的。就像...

    appetizerio 评论0 收藏0

发表评论

0条评论

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