目录

一、构造函数

1.定义

2.new操作符内部执行过程

3.构造函数的缺点

二、对象的原型

1.定义

2.原型的获取方式

3.原型的作用

4.隐式 原型和显示原型

5.创建对象的内存图

6.constructor属性

7.将prototype赋值给新的对象

8.创建对象方案(解决之前构造函数的缺点)

三、原型链

1.定义

2.顶层原型

3.顶层原型来自哪里

4.Object原型

5.Object类是所有类的父类

四、面向对象特性---继承

五、实现继承的几种方案

1.通过原型链实现继承方案

2.借用构造函数方案

3.父类原型赋值给子类原型方案

4.原型式继承方案

5.寄生式继承方案

6.寄生组合式继承方案

六、对象-函数-原型的关系


温馨提示:文章内容较多,请细心观看,希望可以帮助你攻破原型链与继承的知识

一、构造函数

1.定义

构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数

JavaScript中的构造函数是怎么样的?

  1. 构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别;
  2. 那么如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数

2.new操作符内部执行过程

  • 在内存中创建一个新的对象(空对象);
  • 构造函数内部的this,会指向创建出来的新对象;
  • 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;
  • 执行函数的内部代码(函数体代码);
  • 如果构造函数没有返回非空对象,则返回创建出来的新对象;

代码示例:

// 一般创建构造函数 函数名字母开头为大写
function Persion(name, age, height) {this.name = namethis.age = agethis.height = heightthis.eatting = function() {console.log(this.name + '在吃东西');}
}var p1 = new Persion('kk', 18, 1.88)console.log(p1);

3.构造函数的缺点

当某个属性是函数的时候,就会在内存空间创建一个对象,然后用new就会创建不同的对象,这样就会在内存创建大量的对象

二、对象的原型

1.定义

JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象

2.原型的获取方式

  • 通过对象的__proto__属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题)
var obj = { name: "zyk" } // [[prototype]]
var info = {} // [[prototype]]// 给对象中提供了一个属性, 可以让我们查看一下这个原型对象(浏览器提供)
// __proto__
console.log(obj.__proto__) // {}
console.log(info.__proto__) // {}
  • 通过 Object.getPrototypeOf 方法可以获取到
// ES5之后提供的Object.getPrototypeOf
console.log(Object.getPrototypeOf(obj))

3.原型的作用

当我们从一个对象中获取某一个属性时, 它会触发 [[get]] 操作

  1. 在当前对象中去查找对应的属性, 如果找到就直接使用
  2. 如果没有找到, 那么会沿着它的原型去查找 [[prototype]]
// 给obj的[[prototype]]属性加上age属性
obj.__proto__.age = 18console.log(obj.age)

4.隐式 原型和显示原型

  • 隐式原型

我们每一个对象都有一个[[prototype]],这个属性可以称为对象的原型(隐式原型)

  • 显示原型

函数是一个对象 console.log(foo.__ proto__) 函数作为对象来说,它也有[[prototype]] 隐式原          型;

因为函数它是一个函数 所以它还有一个原型prototype(显示原型)

function Person() {}
// 函数是一个对象 console.log(foo.__proto__) 函数作为对象来说,它也有[[prototype]] 隐式原型
// 因为函数它是一个函数 所以它还有一个原型prototype(显示原型)
// 使用new会创建一个对象
// 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性
// 那么也就意味着我们通过Person构造函数创建出来的所有对象的[[prototype]]属性都指向Person.prototype
var p1 = new Person()
var p2 = new Person()console.log(p1.__proto__ === Person.prototype); // true
console.log(p1.__proto__ === p2.__proto__); // true

5.创建对象的内存图

解析:

  • Person作为一个函数,它有一个prototype(显示原型属性),Person.prototype = {}(Person函数的原型对象),所以Person.prototype指向Person函数的原型对象
  • 通过new创建两个对象,他们都有自己的[[prototype]]隐式原型属性,而且通过new操作会将这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性 ,所以p1,p2的__ proto__指向Person.prototype,也就是指向 {}(Person函数的原型对象)

6.constructor属性

foo.prototype = {},其实这个对象不为空(它只是通过Object.defineProperty 将enumerable 设置了为false),这个对象里有一个constructor属性,这个属性又指向构造函数本身

// 1.constructor属性
foo.prototype这个对象中有一个constructor属性
console.log(foo.prototype)
console.log(Object.getOwnPropertyDescriptors(foo.prototype))// Object.defineProperty(foo.prototype, "constructor", {
//   enumerable: true,
//   configurable: true,
//   writable: true,
//   value: "哈哈哈哈"
// })console.log(foo.prototype)// prototype.constructor = 构造函数本身
console.log(foo.prototype.constructor) // [Function: foo]
console.log(foo.prototype.constructor.name)  // fooconsole.log(foo.prototype.constructor.prototype.constructor.prototype.constructor)

我们可以直接在foo.prototype上添加其他属性

// 2.我们也可以添加自己的属性
foo.prototype.name = "why"
foo.prototype.age = 18
foo.prototype.height = 18
foo.prototype.eating = function() {}var f1 = new foo()
console.log(f1.name, f1.age)

内存图的表现:

7.将prototype赋值给新的对象

我们也可以将需要添加的属性添加到一个新对象里,然后再将foo.prototype 指向这个新对象 ,原来的原型对象就会被销毁,但是我们新创建出来的对象没有constructor属性,那么我们可以直接在这个对象上添加,constructor = foo,也可以通过属性描述符来修改,并设置其为不可枚举(enumerable)

代码示例:

// 3.直接修改整个prototype对象
foo.prototype = {// constructor: foo,name: "why",age: 18,height: 1.88
}var f1 = new foo()console.log(f1.name, f1.age, f1.height)// 真实开发中我们可以通过Object.defineProperty方式添加constructor
Object.defineProperty(foo.prototype, "constructor", {enumerable: false,configurable: true,writable: true,value: foo
})

8.创建对象方案(解决之前构造函数的缺点)

将共同的方法在构造函数的原型上添加,这样就能解决创建多个对象的缺点。

但是普通的属性不能添加,因为是创建出来对象独有的属性

注意:因为 p1.running() 是隐式绑定 所以this指向p1对象

代码示例:

function Person(name, age, height, address) {this.name = namethis.age = agethis.height = heightthis.address = address
}Person.prototype.eating = function() {console.log(this.name + "在吃东西~")
}Person.prototype.running = function() {console.log(this.name + "在跑步~") // 因为p1.running()是隐式绑定 所以this指向p1对象
}var p1 = new Person("kk", 18, 1.88, "北京市")
var p2 = new Person("ss", 20, 1.98, "洛杉矶市")p1.eating()
p2.eating()

三、原型链

1.定义

每个对象都有一个特殊的属性[[prototype]],我们可以通过__ proto__ 去访问这个属性。然后这个[[prototype]]属性引用了一个对象,然后这个对象又有自己的特殊的属性[[prototype]],我们可以一直往下访问(直到有一个顶层原型),从而形成了一条原型链

代码示例:

var obj = {name: "kk",age: 18
}// [[get]]操作
// 1.在当前的对象中查找属性
// 2.如果没有找到, 这个时候会去原型链(__proto__)对象上查找obj.__proto__ = {
}// 原型链
obj.__proto__.__proto__ = {}obj.__proto__.__proto__.__proto__ = {address: "广东省"
}console.log(obj.address)

2.顶层原型

当我们console.log(obj.__ proto__) 打印出 [Object: null prototype] {} ,这个就是顶层原型,如果继续往下打印,那么就会输出null

代码示例:

var obj = { name: "kk" }// console.log(obj.address)// 到底是找到哪一层对象之后停止继续查找了呢?
// 字面对象obj的原型是 [Object: null prototype] {}
// [Object: null prototype] {} 就是顶层的原型
console.log(obj.__proto__) // [Object: null prototype] {}// obj.__proto__ => [Object: null prototype] {}
console.log(obj.__proto__.__proto__) // null

3.顶层原型来自哪里

对象字面量的创建其实是通过new Object() 创建的一种语法糖形式

  • 我们先看看var p = new Person() 在内部的执行过程
  1. 首先在内存创建一个对象 var moni = {}
  2. 然后this指向这个对象
  3. 将Person函数的显示原型prototype赋值给前面创建出来的对象的隐式原型 moni.__ proto__ = Person.prototype
  • 再看看var obj2 = new Object() 在内部的执行过程(伪代码)这个moni就是我obj2,所以obj2的[[prototype]]指向Object的prototype,Object的prototype就是顶层原型,Object本质是一个构造函数
  1. var moni = {}
  2. this = moni
  3. moni.__ proto__ = Object.prototype

4.Object原型

[Object: null prototype] {} 原型 的 特殊:

  • 特殊一:该对象有原型属性[[prototype]],但是它的原型属性已经指向的是null,也就是已经是顶层原型了;
  • 特殊二:该对象上有很多默认的属性和方法;

代码示例:

var obj = {name: "kk",age: 18
}var obj2 = {// address: "北京市"
}
obj.__proto__ = obj2

5.Object类是所有类的父类

原型链最顶层的原型对象就是Object的原型对象

function Person() {}// console.log(Person.prototype)
// console.log(Object.getOwnPropertyDescriptors(Person.prototype))console.log(Person.prototype.__proto__) // [Object: null prototype] {}
console.log(Person.prototype.__proto__.__proto__) // null

四、面向对象特性---继承

1.面向对象有三大特性:封装、继承、多态

  • 封装: 我们前面将属性和方法封装到一个类中,可以称之为封装的过程;
  • 继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中);

  • 多态:不同的对象在执行时表现出不同的形态;

2.继承的作用

继承可以帮助我们将重复的代码和逻辑抽取到父类中,子类只需要直接继承过来使用即可

五、实现继承的几种方案

1.通过原型链实现继承方案

思路:

  • 我们先创建父类Person,然后在其prototype中设置eatting方法

  • 再创建子类Stu

  • 创建父类的实例对象p,然后再将p对象赋值给子类Stu的prototype属性

  • 在子类Stu的prototype属性中添加studying方法

代码示例:

// 父类: 公共属性和方法
function Person() {this.name = "why"this.friends = []
}Person.prototype.eating = function() {console.log(this.name + " eating~")
}// 子类: 特有属性和方法
function Student() {this.sno = 111
}var p = new Person()
Student.prototype = pStudent.prototype.studying = function() {console.log(this.name + " studying~")
}// name/sno
var stu = new Student()

原型链继承的弊端 :

  1. 我们通过直接打印对象是看不到这个属性的
  2. 这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题
  3. 不能给Person传递参数,因为这个对象是一次性创建的(没办法定制化)

2.借用构造函数方案

思路:

  • Stu构造函数接收一些属性

  • Stu构造函数里面调用Person.call()

  • Person接收公共属性

  • 设置属性,Person里的this指向Stu对象

代码示例:

// 父类:公共属性和方法
function Person(name, age, friends) {this.name = namethis.age = agethis.friends = friends
}
Person.prototype.eatting = function() {console.log(this.name + '在吃饭');
}
// 子类:私有属性和方法
function Stu(name, age, friends, sno) {// 这里的this指向Stu对象Person.call(this, name, age, friends)this.sno = 111
}// 创建父类对象,并且作为子类的原型对象
var p = new Person()
Stu.prototype = pStu.prototype.studying = function() {console.log(this.name + '在学习');
}

弊端:

  • person类至少执行了两次
  • p对象其实没有必要有这些属性

3.父类原型赋值给子类原型方案

代码示例:

// 父类: 公共属性和方法
function Person(name, age, friends) {// this = stuthis.name = namethis.age = agethis.friends = friends
}Person.prototype.eating = function() {console.log(this.name + " eating~")
}// 子类: 特有属性和方法
function Student(name, age, friends, sno) {Person.call(this, name, age, friends)// this.name = name// this.age = age// this.friends = friendsthis.sno = 111
}// 直接将父类的原型赋值给子类, 作为子类的原型
Student.prototype = Person.prototypeStudent.prototype.studying = function() {console.log(this.name + " studying~")
}// name/sno
var stu = new Student("why", 18, ["kobe"], 111)
console.log(stu)
stu.eating()

弊端:如果给stu.prototype添加一些独有的方法,那么相当于添加到person类里面,当有其他类也指向了person原型,那么他也会拥有stu的独有方法

4.原型式继承方案

1.原型式继承函数的实现

思路:

  • 接收一个需要指向的对象o

  • 有一个构造函数Fn

  • 将Fn的prototype指向o对象

  • 创建一个Fn的实例对象newObj

  • 再返回这个对象

代码实现:

var obj = {name: "kk",age: 18
}var info = Object.create(obj)// 原型式继承函数 实现一:
function createObject1(o) {var newObj = {}Object.setPrototypeOf(newObj, o)return newObj
}// 实现二:
function createObject2(o) {function Fn() {}Fn.prototype = ovar newObj = new Fn()return newObj
}// var info = createObject2(obj)
// 实现三:
var info = Object.create(obj)
console.log(info)
console.log(info.__proto__)

5.寄生式继承方案

代码示例:

var personObj = {running: function() {console.log("running")}
}function createStudent(name) {var stu = Object.create(personObj)stu.name = namestu.studying = function() {console.log("studying~")}return stu
}var stuObj1 = createStudent("kobe")
var stuObj2 = createStudent("james")

弊端:每次创建一个对象,都会在内存创建一个函数对象

6.寄生组合式继承方案

代码示例:

// 原型式函数
function createObject(o) {function Fn() {}Fn.prototype = oreturn new Fn()
}// 封装一个设置constructor属性的函数
function inheritPrototype(SubType, SuperType) {SubType.prototype = Objec.create(SuperType.prototype)Object.defineProperty(SubType.prototype, "constructor", {enumerable: false,configurable: true,writable: true,value: SubType})
}function Person(name, age, friends) {this.name = namethis.age = agethis.friends = friends
}Person.prototype.running = function() {console.log("running~")
}Person.prototype.eating = function() {console.log("eating~")
}function Student(name, age, friends, sno, score) {Person.call(this, name, age, friends)this.sno = snothis.score = score
}inheritPrototype(Student, Person)Student.prototype.studying = function() {console.log("studying~")
}var stu = new Student("kk", 18, ["kobe"], 111, 100)
console.log(stu)
stu.studying()
stu.running()
stu.eating()console.log(stu.constructor.name)

六、对象-函数-原型的关系

先理解这幅图:

解析:

  • Foo作为一个函数,它有prototype属性,并指向Foo的prototype对象,Foo的函数对象有一个constructor属性,它的value指向Foo函数;Foo作为一个对象,它通过var Foo = new Function()创建,它有一个__proto__属性,并指向Function的prototype对象,且Function的prototype对象是一个对象,它会指向顶层原型[Object: null prototype] {}
  • Function是一个函数,它有prototype属性,并指向Function的prototype对象,Function的函数对象有一个constructor属性,它的value指向Function函数;Function作为一个对象,它通过var Function= new Function()创建,它有一个__proto__属性,并指向Function的prototype对象,且Function的prototype对象是一个对象,它会指向顶层原型[Object: null prototype] {}
  • Object是一个函数,它有prototype属性,并指向Object的prototype对象,Object的函数对象有一个constructor属性,里面有许多的方法,它的value指向Object函数;Object作为一个对象,它通过var Object = new Function()创建,它有一个__proto__属性,并指向Function的prototype对象,且Function的prototype对象是一个对象,它会指向顶层原型[Object: null prototype] {}

再看这个:

js原型、原型链、原型链继承详解相关推荐

  1. cc链2(小宇特详解)

    cc链2(小宇特详解) 漏洞环境 jdk8u71 apache commons collection-4.0 与cc链1的区别 cc链2利用链中使用了动态字节码编程来构造poc cc链2没有使用反序列 ...

  2. 区块链技术进阶-深入详解以太坊智能合约语言 solidity(含源码)-熊丽兵-专题视频课程...

    区块链技术进阶-深入详解以太坊智能合约语言 solidity(含源码)-103人已学习 课程介绍         区块链开发技术进阶-深入详解以太坊智能合约语言 solidity视频培训教程:本课程是 ...

  3. 15种区块链共识算法全面详解

    1,摘要 本文尽可能列出所有主要的共识算法,评估各自的优劣之处.共识算法是区块链的核心技术,本文会跟随作者的理解,持续更新.如果读者发现有所遗漏,或是存在错误,希望能通过评论指出. 2,区块链共识算法 ...

  4. vue.js循环for(列表渲染)详解

    vue.js循环for(列表渲染)详解 一.总结 一句话总结: v-for <ul id="example-1"> <li v-for="item in ...

  5. python类继承中构造方法_第8.3节 Python类的__init__方法深入剖析:构造方法与继承详解...

    第8.3节Python类的__init__方法深入剖析:构造方法与继承详解 一.    引言 上两节介绍了构造方法的语法及参数,说明了构造方法是Python的类创建实例后首先执行的方法,并说明如果类没 ...

  6. (117)System Verilog类继承详解

    (117)System Verilog类继承详解 1.1 目录 1)目录 2)FPGA简介 3)System Verilog简介 4)System Verilog类继承详解 5)结语 1.2 FPGA ...

  7. Vue.js - Font Awesome字体图标的使用详解(vue-fontawesome库)

    Vue.js - Font Awesome字体图标的使用详解(vue-fontawesome库) Font Awesome 是一个十分优秀的第三方图标库,我之前也写过文章介绍如何在 html 页面中使 ...

  8. JS - 文件上传组件WebUploader使用详解1(带进度的文件上传

    一.基本介绍 1,什么是 WebUploader? WebUploader 是由百度公司团队开发的一个以 HTML5 为主,FLASH 为辅的现代文件上传组件. 官网地址:http://fex.bai ...

  9. 【职坐标】java面向对象三大特性(二)——继承详解

    [职坐标]java面向对象三大特性(二)--继承详解 a) 什么是继承? i. 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可 b) ...

  10. 基于PHP和JS的AES相互加密解密方法详解(CryptoJS)

    在最近的项目中,前端后台数据交互需要进行加密之后传输使用,以保证系统数据的安全.有关数据加密解密的问题,有很多种加密的方式,在这里我选择了AES的加密方式.特此写下此篇博文,总结讲述下PHP和JS进行 ...

最新文章

  1. Android实现文件下载并自动安装apk包
  2. 【分析】立即购买vs加入购物车,移动电商购买流程差异分析
  3. 河北计算机一级考试题库操作题,年河北计算机一级操作题题库及答案.doc
  4. python bind sock_python SOCKET编程详细介绍
  5. 云栖大会 | 释放计算弹性,阿里云做了很多
  6. iPhone 14“感叹号”设计没跑:屏下Face ID要等到2024年
  7. pyspark分类算法之随机森林分类器模型实践【randomForestClassifier】
  8. weblogic 12c 安装与下载
  9. 电气工程及其自动化用matlab,计算机仿真技术与CAD--基于MATLAB的电气工程(电气工程及其自动化专业精品教材普通高等教育十三...
  10. C#:VARCHART XGantt 5.2.0.167-2022-08-18-UPDATE
  11. 匿名方法和Lambda表达式-天轰穿
  12. 乐谱xml文件转为VOCALOID3的输入文件格式vsqx
  13. BFS算法(广度优先搜索)java
  14. 左神算法基础class6—题目2宽度优先遍历和深度优先遍历
  15. KingbaseES数据库对象管理工具
  16. 多分类任务ovo、ovr及softmax回归
  17. 学习英语的历史性转折
  18. ctfshow web入门 {45-54}
  19. java毕业设计汽车租赁系统mybatis+源码+调试部署+系统+数据库+lw
  20. BUUCTF-Crypto-MD5

热门文章

  1. 架构师培训入门知识体系树
  2. Python去除小数点后面多余的0
  3. 今天是星期五,上班已经三个礼拜了
  4. 2017诺贝尔文学奖揭晓!1901-2017年最全诺奖书单来了
  5. PS一些简单的非主流照片效果
  6. 为什么我们不能坚持?
  7. 专有名词collect
  8. 计算机操作系统试题 地址变换,计算机操作系统试题及答案汇编
  9. 【机器学习】机器学习之决策树(基于ID3、CART挑选西瓜)
  10. 远程服务器连接计算机和用户名填写,windos系统服务器:添加远程连接用户名方法...