目录
创建对象
1. new Object() 方式
因为对象结构无法复用, 每次使用都需要重新定义结构, 适合只使用一次的场景
<script>
let obj = new Object();
obj.name = 'ares5k'
obj.age = 27
obj.say = function () {
console.log('Hello World!')
}
obj.say()
</script>
2. 字面量方式
new Object() 方式的语法糖,依然无法复用, 但是语法更简单
<script>
// 方式 1
let obj = {
name: 'ares5k',
age: 27,
say: function () {
console.log('Hello World!')
}
}
// 方式 2:简化方法定义
let obj2 = {
name: 'ares5k',
age: 27,
say() {
console.log('Hello World!')
}
}
</script>
3. 构造函数方式
(1) 用关键字 this 给对象定义属性和方法
(2) 和普通函数语法一样, 只不过习惯函数名首字母大写, 真正让它具备创建对象能力的, 是关键字 new
(3) 适合结构稳定, 不常改变, 且会重复使用该结构的场景。对象结构可复用, 所以修改要小心, 要考虑到其他使用该结构的地方
<script>
function Person(name, age) {
this.name = name
this.age = age
this.say = function () {
console.log('Hello World!')
}
}
let obj = new Person('ares5k', 27)
</script>
4. class方式
1. 定义方法不能写 function 关键字
2. 定义属性, 要写在固定方法 constructor() 中
3. class方式其实就是构造函数方式的一个语法糖
<script>
class Obj{
constructor(name, age){
this.name = name
this.age = age
}
say(){
console.log(this.name) // 结果:ares5k
console.log(this.age) // 结果:27
console.log('Hello World!') // 结果:Hello World!
}
}
let obj = new Obj('ares5k', 27)
obj.say()
</script>
5. 特殊场景下简化对象方式
<script>
// 定义变量和函数
let name = 'ares5k'
let age = 27
let say = function(){console.log('Hello World!' + this.name)}
// 正常方式
let obj = {
name: name,
age: age,
say: say
}
// 简化对象方式
let obj2 = {
name,
age,
say
}
</script>
使用和扩展对象
1. 使用对象的属性和方法
<script>
// 创建对象
let obj = {
name: 'ares5k',
say(){
console.log('Hello World!')
}
}
// 使用属性的两种方式
console.log(obj.name) // 结果:ares5k
console.log(obj['name']) // 结果:ares5k
// 调用方法的两种方式
obj.say() // 结果:Hello World!
obj['say']() // 结果:Hello World!
</script>
2. 扩展对象的属性和方法
<script>
// 创建对象
let obj = {
name: 'ares5k',
say(){
console.log('Hello World!')
}
}
// 扩展属性的两种方式
obj.age = 27
obj['hobby'] = 'Jiu-Jitsu Brasileiro'
// 扩展方法的两种方式
obj.walk = function(){ console.log('走路!') }
obj['sport'] = function(){ console.log('运动!') }
// 测试
console.log(obj.name) // 结果:ares5k
console.log(obj.age) // 结果:27
console.log(obj.hobby) // 结果:Jiu-Jitsu Brasileiro
obj.say() // 结果:Hello World!
obj.walk() // 结果:走路!
obj.sport() // 结果:运动!
</script>
原型对象
1. 什么是原型对象
每个类型都有一个对应的原型对象( prototype ), 该类型所实例化出来的所有对象, 其内部都保存一个默认指向这个原型对象的引用( __proto__ )
作用:
(1) 原型对象可以用来共享方法, 减少内存占用
(2) 原型对象可以用来扩展内置对象
(3) 可以通过原型链和方法查找机制来模拟继承的效果
(4) object 对应的原型对象没有 __proto__ 属性, 它是原型链的重点
代码实验:
<script>
// 定义构造函数
function Person(name){
this.name = name
this.say = function(){
console.log('Hello World!!')
}
}
// 创建三个实例对象
let person1 = new Person('p1')
let person2 = new Person('p2')
let person3 = new Person('p3')
// 判断每个对象之间, 对象地址、对象属性、对象方法是否相同
console.log(person1 === person2 || person1.say === person2.say || person1.name === person2.name) // 结果:false
console.log(person2 === person3 || person2.say === person3.say || person2.name === person3.name) // 结果:false
// 判断每个对象默认的内部引用 __proto__ 指向的原型对象是否是同一个
console.log(person1.__proto__ === person2.__proto__) // 结果:true
console.log(person2.__proto__ === person3.__proto__) // 结果:true
// 类型对应的原型对象和对象内指向的是否是同一个
console.log(Person.prototype === person1.__proto__) // 结果:true
</script>
通过上面代码, 可以得到这个内存模型图:
上图可印证我们的观点, 每个类型都有一个对应的原型对象( prototype ), 该类型所实例化出来的所有对象, 其内部都保存一个默认指向这个原型对象的引用( __proto__ )
2. 原型对象的结构、原型链和查找机制
查看原型对象结构:
<script>
// 定义构造函数
function Person(){}
// 打印原型对象结构
console.dir(Person.prototype)
</script>
运行结果 ( 原型对象结构 ):
原型对象内部有两个主要属性 constructor 和 __proto__
constructor:
指向的是该原型对象所对应的构造函数, 本例中原型对象的 constructor 属性指向的就是与其对应的构造函数对象 Person
__proto__:
原型对象内的 __proto__ 是形成原型链的重要属性, 通常指向父构造函数所对应的原型对象, 本例中构造函数 Person 没有
显示指定父构造函数, 那么其默认的父构造函数就是 Object, Object 所对应的原型对象很特殊, 它没有 __proto__ 属性
是整条原型链的终点
对象结构图如下:
图中黄色部分的这条链路, 就被称为原型链。
查找机制就是沿着原型链查找:
(1) 访问对象的属性和方法时, JS 引擎会先在对象本身查找该属性和方法, 找不到执行 (2)
(2) 在原型对象上查找, 找不到执行 (3)
(3) 在原型对象的 __proto__ 属性指向的原型对象查找 (既父构造函数对应的原型对象中查找)
反复执行 (3), 直到找到属性和方法为止, 或者一直找不到, 但到了链路终点, 既父类是 object ( object原型对象没有 __proto__ )
修改 <什么是原型对象> 章节中的例子:
通过查找机制的特性, 我们可以把例子中一些能够共享的东西, 直接创建在原型对象中, 从而节约内存。
在 <什么是原型对象> 章节的例子中, 3个对象的 say() 方法逻辑相同, 所以可以定义在原型对象中, 让该类型的所有对象都可以共享访问
代码如下:
<script>
// 定义构造函数
function Person(name){
this.name = name
}
// 将方法定义在该函数对应的原型对象中
Person.prototype.say = function(){
console.log('Hello World!!')
}
// 创建三个实例对象
let person1 = new Person('p1')
let person2 = new Person('p2')
let person3 = new Person('p3')
// 方法比较
console.log(Person.prototype.say === person1.say) // 结果:true
console.log(person1.say === person2.say) // 结果:true
console.log(person2.say === person3.say) // 结果:true
</script>
通过上面代码, 又可以得到内存模型图:
通过上图可以看出, say() 方法从原来的 3 份, 变成了 1 份, 减少了内存占用。
3. 继承
(1) 通过原型链和查找机制来模拟继承
<script>
// 创建父构造函数
function Father(name) {
this.name = name
}
// 为父构造函数的原型对象添加方法
Father.prototype.money = function () {
console.log(this.name + ' 赚钱')
}
// 创建子构造函数
function Son(name) {
// 调用父构造函数 (目的是继承父属性)
Father.call(this, name)
}
// 修改子构造函数对应的原型对象
// 1. 指定父类, 让原型对象中的 __proto__ 指向 Father 对应的原型对象 (目的是通过指定原型链来继承父方法)
// 2. 因为是整体覆盖了 prototype, 所以要把原型对象中的 constructor 指回到 Son 构造函数对象
Son.prototype = {
__proto__: Father.prototype,
constructor: Son
}
// 为子构造函数的原型对象添加方法
Son.prototype.study = function () {
console.log(this.name + ' 学习')
}
let son = new Son('ares5k')
son.money() // 结果:ares5k 赚钱
son.study() // 结果:ares5k 学习
</script>
完整结构图如下:
例中几个重点:
① 原型链:Son.prototype → Father.prototype → Object.prototype
② 调用 study:Son 本身没有 study 方法, 接着去 Son.prototype 中找, 找到并执行 study 方法
③ 调用 money:Son 本身没有 money 方法, 接着去 Son.prototype 中找也没有, 再去 Father.prototype 中找, 找到并执行
④ 原型对象中 this 的指向:整个原型链中的 this 都是相同的, 都是实例对象, 既 new Son() 这个对象
(2) 通过语法糖来实现继承, 其本质还是构造函数 + 原型链方式
<script>
// 创建父类
class Father {
constructor(name) {
this.name = name
}
}
// 为父类的原型对象添加方法
Father.prototype.money = function () {
console.log(this.name + ' 赚钱')
}
// 创建子类
class Son extends Father {
}
// 为子类的原型对象添加方法
Son.prototype.study = function () {
console.log(this.name + ' 学习')
}
let son = new Son('ares5k')
son.money() // 结果:ares5k 赚钱
son.study() // 结果:ares5k 学习
</script>
通过行为来理解 new
通过 new 创建对象有三种情况:
1. 构造函数没有返回值, new 会返回一个构造函数类型的对象
2. 构造函数有返回值, 但是返回值不是对象类型, new 也会返回一个构造函数类型的对象
3. 构造函数有返回值, 并且返回值是对象类型, 那么 new 会直接返回这个对象
代码证实:
<script>
// 定义无返回值构造
function Person(name) {
this.name = name
}
// 定义有返回值构造 - 返回值为对象以外的场合
function Man(sex) {
this.sex = sex
return '字符串'
}
// 定义有返回值构造 - 返回值为对象的场合
function People(name) {
this.name = name
return {age: 27}
}
console.dir(new Person('ares5k')) // 结果 Person: {name: ares5k}
console.dir(new Man('man')) // 结果 Man: {sex: man}
console.dir(new People('ares5k')) // 结果 Object: {age: 27}
</script>
模拟 new 的行为:
<script>
// 定义构造函数
function Person(name){
this.name = name
}
// 模拟 new
function dummyNew(fn, arg){
// 1. 创建空对象 obj
let obj = {}
// 2. 改变 obj 的原型指向
obj.__proto__ = fn.prototype
// 3. 调用构造函数并修改构造函数内的 this 指向为 obj
let result = fn.call(obj, arg)
// 4. 根据构造函数的返回结果,来决定具体返回对象
return result instanceof Object ? result : obj
}
// 测试
let person = dummyNew(Person, 'ares5k')
console.dir(person)
</script>