文章目录
目录
为了直观表达它们之间的关系,我们先来看一张图!!具体内容后面细谈!!
为了直观表达它们之间的关系,我们先来看一张图!!具体内容后面细谈!!
一、概念
1.构造函数是什么?
JavaScript中的构造函数是一种特殊的函数,用于创建并初始化对象。在JavaScript中,任何函数都可以作为构造函数,只需要在调用时使用new
关键字来标识。当函数作为构造函数调用时,它会创建一个新的对象,并将其绑定到this
关键字上,然后返回这个对象,而不是返回函数本身。
例如,以下是一个构造函数:
function Person(name, age) {
this.name = name;
this.age = age;
}
2.实例对象是什么?
在JavaScript中,构造函数用于创建对象,并且通过构造函数创建的对象被称为实例对象。可以通过关键字"new"和构造函数来创建实例对象。
通过使用这个构造函数,可以创建Person对象的实例。下面的代码展示了如何创建一个Person对象的实例:
var person1 = new Person("John", 30);
3.原型对象是什么?
JavaScript中每个函数都有一个特殊的属性,称为原型对象(prototype object)。这个原型对象是一个普通的对象,它包含了该函数所有实例共享的属性和方法。当一个函数被定义时,JavaScript会自动为这个函数创建一个原型对象。
当我们使用关键字 new
来创建一个对象时,JavaScript会将这个对象的原型属性指向构造函数的原型对象。这个原型对象就是构造函数创建的所有对象所共享的属性和方法的来源。
4.原型链是什么?
JavaScript 中的每个对象都有一个内部属性 __proto__,它指向了该对象的原型。原型可以看做是对象的模板,包含了对象共有的属性和方法。
当我们访问一个对象的属性时,如果对象本身没有该属性,JavaScript 就会沿着对象的原型链向上查找,直到找到该属性为止。如果最终没有找到该属性,那么这个对象就没有该属性。
原型链就是由对象的 __proto__ 属性构成的一条链。当我们访问对象的属性时,JavaScript 引擎首先查找对象本身是否有该属性,如果没有,则去对象的原型中查找,如果还没有,则去原型的原型中查找,一直找到 Object.prototype 为止。Object.prototype 是原型链的终点,如果还没找到该属性,则返回 undefined。
二、构造函数和实例对象的使用
构造函数的使用
1. 构造函数首字母建议大写
=> 目的: 为了和其他函数进行区分
2. 构造函数调用可以不写小括号
=> 当你调用构造函数的时候, 如果不需要传递参数, 可以不写小括号
=> 建议你全都写上
3. 构造函数内的 this 指向
=> 因为构造函数和 new 关键字连用
=> 因为 new 关键字的能力, 所以构造函数内的 this 指向那个被自动创建出来的对象
=> 又因为这个对象被自动返回了出来, 在函数调用前面定义变量接受了
=> 我们管 构造函数 创建出来的这个对象, 叫做 实例对象
=> 我们管 构造函数 创建对象的过程叫做 实例化 的过程
4. 构造函数的调用必须和 new 关键字连用
=> 因为只有和 new 关键字连用了, 才会有 实例化 的能力
=> 我们书写构造函数的目的, 就是为了 实例化 对象
5. 构造函数内不要写 return
=> 如果你 return 了一个基本数据类型, 写了白写
=> 如果你 return 了一个复杂数据类型, 构造函数白写
示例:
function Person(name, age) {
this.name = name
this.age = age
}
const p1 = new Person('Jack', 18)
console.log(p1)
三、构造函数的合理性(引出原型对象)
考虑构造函数的合理性
+ 当我们书写构造函数的时候
+ 如果把方法也书写在构造函数体内
+ 随着创建的实例对象越来越多, 会造成额外的资源浪费
代码示例:
function Person(name) {
// 向 实例对象 上添加一个 属性
this.name = name
// 向 实例对象 上添加一个 方法
this.sayHi = function () { console.log('hello world') }
}
const p1 = new Person('Jack')
const p2 = new Person('Rose')
console.log(p1.name, p2.name)
p1.sayHi()
p2.sayHi()
想办法解决构造函数内的不合理
1.过程第一步
+ 不在构造函数内书写方法了
+ 单独在外面创建一个函数, 把地址放进去赋值
+ 缺点:
=> 当一个实例对象所需要的方法过多, 我们需要在全局暴露很多的变量名
=> 当多个构造函数出现的时候, 在全局的若干变量名会出现冲突
代码示例:
在外面创建函数
function sayHi() {
console.log('hello world')
}
function Person(name) {
// 向 实例对象 上添加一个 属性
this.name = name
// 不在构造函数内书写方法了
// 不进行函数的定义阶段了, 只是拿到函数地址进行赋值
this.sayHi = sayHi
}
const p1 = new Person('Jack')
const p2 = new Person('Rose')
console.log(p1.name, p2.name)
p1.sayHi()
p2.sayHi()
2. 过程第二步
+ 不把单独书写出来的函数放在全局, 因为可能有多个
+ 我单独创建一个对象, 把这些函数都放在对象内
+ 缺点:
=> 每一个构造函数, 除了构造函数名占用变量以外
=> 每一个对象都会占用一个变量名
代码示例:
const PersonObj = {
sayHi: function () { console.log('hello world') },
play () { console.log('玩游戏') }
}
function Person(name) {
// 向 实例对象 上添加一个 属性
this.name = name
// 不在构造函数内书写方法了
// 不进行函数的定义阶段了, 只是拿到函数地址进行赋值
// 不要忽略有一个对象
this.sayHi = PersonObj.sayHi
this.play = PersonObj.play
}
const p1 = new Person('Jack')
const p2 = new Person('Rose')
console.log(p1.name, p2.name)
p1.sayHi()
p2.sayHi()
p1.play()
p2.play()
3.过程第三部
+ 概念: 每一个函数天生自带一个属性叫做 prototype(原型), 是一个对象数据结构
+ 构造函数也是函数, 构造函数也有 prototype
+ 这个 prototype 内带有一个 constructor 属性, 表示是哪一个构造函数的 prototype
+ 构造函数的原型上书写的属性和方法, 该构造函数的实例对象不需要任何操作, 可以直接使用
代码示例:
function Person(name) {
// 向 实例对象 上添加一个 属性
this.name = name
}
// 构造函数 Person 已经创建完毕了
// 可以向这个对象内添加成员
Person.prototype.sayHi = function () { console.log('hello world') }
Person.prototype.play = function () { console.log('玩游戏') }
// console.log(Person.prototype)
const p1 = new Person('Jack')
const p2 = new Person('Rose')
console.log(p1.name, p2.name)
console.log(p1, p2)
p1.sayHi()
p2.sayHi()
p1.play()
p2.play()
结论: 解救构造函数内的不合理
+ 当我需要属性(相似但不一样)的时候, 书写在构造函数体内
+ 当我需要方法(每个方法都一样)的时候, 书写在构造函数的 prototype(原型) 上
四、原型对象
+ 每一个函数天生自带一个属性叫做 prototype, 是一个对象数据结构
+ 每一个对象天生自带一个属性叫做 __proto__, 指向所属构造函数的原型(prototype)
+ 对象的访问机制
=> 当你需要访问一个对象的成员的时候
=> 首先在自己身上查找, 如果有, 直接使用, 停止查找
=> 如果没有, 会自动去到自己的 __proto__(也就是所属构造函数的 prototype 上查找)
五、原型链
原型链
+ 只要是 数组数据类型, 所属构造函数就是 Array
+ 只要是 函数数据类型, 所属构造函数就是 Function
+ 只要是 正则数据类型, 所属构造函数就是 RegExp
+ 只要是 对象数据类型, 所属构造函数就是 Object
+ 原型链: 对象的 __proto__ 串联起来的对象链状结构
+ 目的: 对象访问机制
=> 当你访问一个对象成员的时候, 首先在自己身上查找, 如果有, 直接使用, 停止查找
=> 如果没有, 会自动去到自己的 __proto__ 查找, 如果有直接使用, 停止查找
=> 如果还没有, 就再去 __proto__ 上查找, 如果有直接使用, 停止查找
=> 以此类推, 直到 Object.prototype 都没有的时候, 返回 undefined
问题1: p 的 __proto__ 指向谁 ?
=> 对象的 __proto__ 指向所属构造函数的 prototype
=> 因为 p 的所属构造函数是 Person
=> p.__proto__ === Person.prototype
问题2: Person.prototype 是一个对象, __proto__ 指向谁 ?
=> Person.prototype 是一个对象数据类型
=> 它多数的构造函数就是 Object
=> Person.prototype.__proto__ === Object.prototype
问题3: Person 是函数的同时也是一个对象, __proto__ 指向谁 ?
=> Person 是一个函数数据类型
=> 它所属的构造函数就是 Function
=> Person.__proto__ === Function.prototype
问题4: Object.prototype 是一个对象, __proto__ 指向谁 ?
=> Object.prototype.__proto__ 指向 null
=> 因为我们管 Object.prototype 叫做顶级原型
问题5: Object 是函数的同时也是一个对象, __proto__ 指向谁 ?
=> Object 是一个内置构造函数, 也是一个构造函数, 也是一个函数
=> 只要是函数, 所属的构造函数就是 Function
=> Object 所属的构造函数就是 Function
=> Object.__proto__ === Function.prototype
问题6: Function.prototype 是一个对象, __proto__ 指向谁 ?
=> Function.prototype 是一个对象数据类型
=> 它所属的构造函数就是 Object
=> Function.prototype.__proto__ === Object.prototype
问题7: Function 是函数的同时也是一个对象, __proto__ 指向谁 ?
=> Function 是一个内置构造函数, 也是一个构造函数, 也是一个函数
=> 只要是函数, 所属的构造函数就是 Function
=> Function 自己是自己的实例, 自己是自己的构造函数
=> 我们管 Function 叫做顶级函数
=> Function.__proto__ === Function.prototype
代码示例:
function Person() {
this.name = 'Jack'
this.age = 18
this.gender = '男'
}
Person.prototype.sayHi = function () {}
const p = new Person()
console.log(p.__proto__ === Person.prototype)
console.log(Person.prototype.__proto__ === Object.prototype)
console.log(Person.__proto__ === Function.prototype)
console.log(Object.prototype.__proto__ === null)
console.log(Object.__proto__ === Function.prototype)
console.log(Function.prototype.__proto__ === Object.prototype)
console.log(Function.__proto__ === Function.prototype)