在了解"原型"之前,你需要知道"面向对象"、"构造函数"等相关知识。
面向对象
- 面向对象实际是一种编程方式、思想。
- 提到面向对象,不得不提的是面向过程。
- 区别:
- 面向过程,关注的是每一步如何实现。
- 面向对象,只需要关注对象能做什么事。
- 举个例子:
- 比如你想吃冰淇淋
- 面向对象:你直接去超市或者商店,找到冰淇淋,付钱,开吃。
- 面向过程:你要准备冰淇淋的材料,还要知道制作的过程,制作好后,才能吃。
你可能还并没有完全了解面向对象的概念,但是没关系。再举个例子:
- 比如你想吃拉面,你可以选择自己制作,也可选择去面馆。
- 自己做就是“面向过程”,因为你需要买面,和面,拉面,煮面···最后吃面。
- 去面馆就是“面向对象”,因为你只需要和老板说,我要吃“牛肉拉面”,等一会就能吃面了。
你也可以去阿里云开发者社区查看大佬的解释,或自行百度。
对象的创建方式
内置构造函数
| var obj = new Object(); |
| obj.year = 2022; |
| |
| obj.hiFn = function() { |
| console.log("你好,2022!") |
| } |
字面量
| |
| var obj1 = { |
| name: 'tim', |
| age: 22 |
| } |
工厂函数
| |
| function factory() { |
| |
| var obj = new Object(); |
| |
| obj.name = 'tim'; |
| obj.age = 22; |
| |
| return obj; |
| } |
| |
| var o1 = factory(); |
| var o2 = factory(); |
自定义构造函数
| |
| function Person(name, age) { |
| |
| |
| |
| this.name = name; |
| this.age = age; |
| |
| |
| } |
| |
| var p1 = new Person('tim', 22); |
| console.log(p1); |
构造函数知识点
- 构造函数与普通函数类似,之不过调用时需要与
new
连用
- 首字母用大驼峰命名,如
Person
、Animal
···
- 与
new
连用时,内部会自动创建一个对象,并返回对象,不需要写return
- 构造函数内部的
this
,指向当前实例。函数里this
的指向,由调用方式决定:
- 作为声明式函数调用:
this ⇒ window
- 作为事件函数调用:
this ⇒ 事件源
- 作为对象的方法调用:
this ⇒ 调用方法的实例对象
- ···
- 书写构造函数时,除了添加属性也可以添加方法
- 每new一次实例,方法都会单独开辟一块空间,即使是同一个方法
- 因此这会造成同一个方法占用多个内存空间,所以引入了
原型 prototype
原型
prototype
- 原型的出现,就是为了解决构造函数的缺点:同一个方法占用多个内存空间
- 每一个函数都自带一个属性,叫
prototype
,是一个对象空间,构造函数也有
- 可以通过函数名访问,也可以向该空间添加属性、方法,如:
Person.prototype
、Person.prototype.name = "tim"
- 在
prototype
虽然是通过函数添加的内容,但内容却是给实例使用的
| |
| function Person(name, age) { |
| this.name = name; |
| this.age = age; |
| } |
| |
| Person.prototype.sleep = function() { |
| console.log("人都需要睡觉,7-8小时为宜。"); |
| } |
| |
| var p1 = new Person("tim", 22); |
| console.log(p1.name); |
| console.log(p1.age); |
| console.log(p1.sleep); |
proto
- 每一个对象都自带一个属性,叫
__proto__
,也是一个对象空间
- 实例对象也有
__proto__
,这个空间是指向该构造函数的prototype
- 所以,
__proto__
的地址与构造函数的prototype
的地址是同一个地址
- 当实例访问某个属性/方法时,会从自身查找。没找到会自动去
__proto__
里找
- 通常,在书写构造函数时:
原型链
__proto__
里面有一个成员叫constructor
,是指向当前对象所属的构造函数
- 如果,当一个对象不知道是谁构造的,就认为是由
Object
构造的
- 因为,
Object
是Js
中的顶级构造函数,有句话叫:"万物皆对象"
- 所以,当找到
Object.prototype
时就到顶了,Object.prototype.__proto__
结果是null
- 由此形成了一个链状结构,就是"原型链"
- 原型链的规则:
- 访问规则
- 当访问一个对象的属性或方法时,会先从自身查找
- 如果没有找到,就会自动去
__proto__
里查找
- 如果还没找到,就会去
__proto__
的__proto__
里面查找
- 一直找到
Object.prototype
里面都没有找到,那么就会返回undefined
- 赋值规则
- 当需要给一个对象的属性或方法赋值时,会先从自身查找
- 如果找到了,就修改属性的值
- 如果没找到,就直接添加该属性并赋值
- 不会去
__proto__
里
继承
原型继承
- 通过改变原型链的方式达到继承
- 就是将子类的原型指向父类的实例,
Son.prototype = new Father();
- 缺点:
- 子类实例若修改了方法,会影响其他子类的方法,因为共用一个地址空间
| |
| function Father(name){ |
| this.name = name; |
| } |
| |
| Person.prototype.sayHi = function(){ |
| console.log('hello, my son!') |
| } |
| |
| function Son(age){ |
| this.age = age; |
| } |
| |
| Son.prototype = new Father('Jack'); |
| |
| var s1 = new Son(18); |
| console.log(s1); |
构造函数继承
- 在子类的构造函数中,使用父类的构造函数,并修改
this
指向,指到子类实例
- 使用
call
来改变函数内部this
指向,call(子类实例,传递给函数的参数, ...)
- 优点:可以将继承自父类的属性直接写在自身
- 缺点:
- 只能继承父类函数体里的内容,不能继承父类原型上的内容
- 因为,函数体里通常写的是属性,而方法通常是写在原型上的
| |
| function Father(name, gender){ |
| this.name = name; |
| this.gender = gender; |
| } |
| Father.prototype.sayHi = function(){ |
| console.log('hello, my son!'); |
| } |
| |
| function Son(age, name, gender){ |
| this.age = age; |
| |
| Person.call(this, name, gender); |
| } |
| |
| var s1 = new Son(18, "小红", '女'); |
组合继承
- 由于上述两种方法,都有各自的缺点,所以将两者结合,就是组合继承
- 先使用构造函数继承,将属性继承到自身
- 再使用原型继承,将方法继承到自身
- 缺点:
- 子类自身原型上的方法,会被覆盖
- 所以,必须要继承以后,再为子类添加原型方法
| |
| function Father(name, gender){ |
| this.name = name; |
| this.gender = gender; |
| } |
| Father.prototype.sayHi = function(){ |
| console.log('hello, my son!'); |
| } |
| |
| function Son(age, name, gender){ |
| this.age = age; |
| |
| Father.call(this, name, gender); |
| } |
| |
| Son.prototype = new Father(); |
| |
| Son.prototype.sonFn = function(){ |
| console.log("我是子类自身的方法") |
| } |
| |
| var s2 = new Student(20, "小明", '男'); |
ES6的类语法
-
学习ES6
的class
语法之前,先复习下之前使用构造函数如何定义属性与方法
| |
| function Human(name, age){ |
| this.name = name; |
| this.age = age; |
| } |
| |
| Human.prototype.eat = function(){ |
| console.log("一顿不吃饿得慌") |
| } |
-
然后再来对比,ES6
的类语法
| |
| class Human{ |
| |
| constructor(name, age) { |
| this.name = name; |
| this.age = age; |
| } |
| |
| eat(){ |
| console.log("一顿不吃饿得慌") |
| } |
| |
| static year = 2022; |
| static sayHi = function() { |
| console.log("Hello world!"); |
| }; |
| } |
| |
| var h1 = new Human("小明", 20); |
| console.log(Human.year); |
| console.log(Human.sayHi()); |
ES6继承
- 可以使用
extends
关键字:class Son extends Father{ }
- 继承父类使用
super
关键字:super(name, age);
,并且必须先写
| |
| class Father{ |
| constructor(name, age) { |
| this.name = name; |
| this.age = age; |
| } |
| sayHi() { |
| console.log("Hello, my son!") |
| } |
| } |
| |
| class Son extends Father{ |
| constructor(gender, name, age) { |
| |
| super(name, age); |
| |
| this.gender = gender; |
| } |
| |
| sayHello() { |
| console.log("Hello, my father!") |
| } |
| } |
| |
| var s1 = new Son("男", "李磊", 22) |
| console.log(s1); |
- 子类通过
extends
表示要继承父类
- 然后通过
super
来继承父类的属性
- 父类的方法不需要写其他的,会通过原型链自动查找继承

【自己总结的,难免有错,仅供参考】
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律