JavaScript ES5 原型链(一、原型)

168 阅读6分钟

JavaScript ES5 原型链(一、原型)

JavaScript 认识普通对象原型

JavaScript 中每个对象都是含有一个内置属性的 [[prototype]] ,我们是可以通过这个内置的对象实现指向另外一个对象

这个对象就是我们的对象具有的原型对象

实现获取对象的原型对象的方法有:

  • Object.getProrotypeof(obj) 标准的用法
  • obj.__proto__ 不标准的用法
var obj = {
    name: "76433",
    age: 18,
    school: "university of post in chongqing",
}
​
console.log(obj)
  • 首先通过上面的打印输出,我们的调试者工具的话,是会出现原型的,就是我们的 prototype
  • 我们实现获取对象中的原型的方法有: obj.__proto__ 或者 Object.getPrototypeOf(obj) 就可以实现获取对象原型
// 实现获取原型的方法
console.log(Object.getPrototypeOf(obj))
​
// 或者说通过 __proto__ 来实现获取对象原型
console.log(obj.__proto__)
​
console.log(Object.getPrototypeOf(obj) === obj.__proto__)  // true
  • 我们需要注意的是,我们的每一个对象都是含有原型的

原型具有的作用又是什么呐???

当我们通过一个 [[get]] 方法来实现获取我们的一个对象中的属性的 value 时(obj.name

  1. 它实现的的查找规则是现在自己的对象中进行查找我们的值,如果找到了,那就直接返回
  1. 否则就是进入原型对象,在原型对象中进行查找
  2. 如果在原型对象中还是没有查找得到,那么最后就是查找到的是 undefined
var obj = {
    name: "76433",
    age: 18,
    school: "university of post in chongqing",
}
​
Object.getPrototypeOf(obj).message = "my github is juwenzhang"  // 这个是标准的写法
// obj.__proto__.message = "my github is juwenzhang"  // 非标准的写法console.log(obj)
console.log(obj.message)
  • 通过运行结果我们就可以知道一点的是,我们的 obj 对象本身是不具备 message 属性的
  • 但是最终还是可以出来值,这个就是因为我们在原型对象上面添加了这个属性,所以说就可以查找到

image-20241110045047717.png

JavaScript 函数的原型

JavaScript 中的函数是十分特殊的

不亏是以函数作为头等公民的一门编程语言

函数的原型对象和纯对象的话是有所不同的,是因为,我们的函数既可以看作是一个对象,又可以是一个函数

  • 当看作是一个对象的时候,我们的函数是具有和上面的纯对象一样的获取原型的方法的

    • 这个原型是我们的 [[prototype]]
    • 获取的方法 Object.getPrototypeof(func_name) | func_name.__proto__
  • 当看作的是一个函数的时候(除了箭头函数

    • 每个函数都是具有:prototype 属性
    • 获取方式为: func_name.prototype
var func = function () {}
​
​
console.dir(func)
​
console.dir(Object.getPrototypeOf(func))
​
console.log(func.prototype)

image-20241110051313400.png

JavaScript 函数的new操作符

new操作符进行操作含有:

  • 创建一个空对象,这个空对象就是我们的 [[prototype]] 的隐式原型
  • 将空对象赋值给 this
  • 将函数的显式原型**prototype**赋值给对象作为他的隐式原型 __proto__
  • 执行函数中的代码体
  • 将这个对象默认返回
var Func = function () {
    return this
}
​
​
var func = new Func();  // 常说的就是我们编程人员最不缺的就是对象,缺的话直接 new 一个出来,func 就是对象console.log(Func.prototype === func.__proto__);  // true
console.log(Func.prototype === Object.getPrototypeOf(func));  // true
  • 在来一个例子吧,来讨论我们的这种操作的具体作用
var Func = function () {}
​
​
var func = new Func()
var func1 = new Func()
var func2 = new Func()
var func3 = new Func()
​
console.log(Object.getPrototypeOf(func) === Func.prototype)  // true
console.log(Object.getPrototypeOf(func1) === Func.prototype)  // true
console.log(Object.getPrototypeOf(func2) === Func.prototype)  // true
console.log(Object.getPrototypeOf(func3) === Func.prototype)  // true// 通过上面的操作的话,类推就出现了、
console.log(Object.getPrototypeOf(func1) === Object.getPrototypeOf(func3))  // true

JavaScript 将方法添加到原型上

以前实现我们的一次性创建多个对象的方法是:

function Stu(name, age) {
    this.name = name;
    this.age = age;
​
    this.running = function () {
        console.log("running")
    }
​
    this.studying = function () {
        console.log("studying")
    }
}
​
var stu1 = new Stu("juwenzhang", 18);
var stu2 = new Stu("76433", 18);
var stu3 = new Stu("水逆信封", 18);
  • 我们使用这种方法的弊端:

    • 通过我们的上章节的描述,函数在执行的时候,都是会开辟一个内存空间的
    • 如果通过上面的方法来进行创建我们的三个实例对象,那么最后导致的就是同样的功能代码,连续被开辟
    • 了多个内存空间,导致内存的浪费,这个时候,我们的解决方案,就是在原型上面添加一个方法
    • 因为在原型上添加的方法,最终还是实现的只是指向的是一块内存区域,这样大大的减少以及优化了我们的代码

现在的操作就是在原型上实现添加方法,减少不必要的内存浪费

function Stu(name, age) {
    this.name = name;
    this.age = age;
​
    Stu.prototype.running = function () {
        console.log("running")
    }
​
    Stu.prototype.studying = function () {
        console.log("studying")
    }
}
​
// new 操作符实际上的话,底层实现的最终的一步源码就是: Object.getPrototypeOf(stu1) = Stu.prototype
var stu1 = new Stu("juwenzhang", 18);
var stu2 = new Stu("76433", 18);
var stu3 = new Stu("水逆信封", 18);
​
// 这个时候的调用方法就有:
Object.getPrototypeOf(stu1).running()
Stu.prototype.running()
stu1.running()
console.log(Object.getPrototypeOf(stu1))

那么上面的实现的原理是什么呐???

首先的是我们的通过使用了 new 操作符后,下一步进行的操作就是: 实例对象的原型__proto__ 指向 函数的 prototype

又通过上面的第一点的讲解,对于普通对象而言查找属性和方法的机制,本级没有,那就往原型中进行寻找,有的话就返回,没有就是undefined

同时又因为__proto__ 指向的是 prototype ,所以说,最终的前者可以访问得到其内部的方法或者属性

基本的流程就是这样的

我们还需要主要的一点就是:两个对象之间的进行相互赋值的原理

var obj01 = {
    name: "juwenzhang"
}
​
var obj02 = {
    age: 19,
    height: 165
}
​
obj01 = obj02
​
console.log(obj01.name, obj01.age, obj02.height)  // undefined 19 165

为什么会出现这种情况耶??

obj01 = obj02

首先第一步做的是令我们的 obj01 = null

然后实现的才是将我们的 obj02 的内存地址赋值给 obj01

image-20241110062553895.png

我们需要注意的一点就是,原型对象和实例对象之间之间的赋值操作是全局堆区中进行的

但是实际上的话,两者的赋值本质和上面的思想原理是一样的呐!!!

JavaScript 函数显式原型(prototype)中的 constructor 属性

在函数的显式原型 prototype 指向的 显式原型对象中,是具有一个属性的,就是 constrcutor

这个属性最终实现的是指向我们函数本身,实现的循环套用,但是循环套用的话,我们的 V8 引擎是具有解决方案的

function Foo() {}
​
var Foo_prototype = Foo.prototypeconsole.log(Foo_prototype.constructor === Foo)  // true
​
​
var foo = new Foo()
console.log(Object.getPrototypeOf(foo) === Foo.prototype)  // true
console.log(Object.getPrototypeOf(foo).constructor === Foo.prototype.constructor)  // true
console.log(Object.getPrototypeOf(foo).constructor === Foo)  // true

补充原型对象和实例对象之间的详细图解

/**
 * 创建的是 Foo 构造函数
 * @param {string} name
 * @param {number} age
 * @returns undefined
 */
function Foo(name, age) {
    this.name = name
    this.age = age
​
    Foo.prototype.running = function() {
        console.log(`${this.name} is running`)
    }
​
    Foo.prototype.studying = function() {
        console.log(`${this.name} is studying`)    }
}
​
/**
 * 开始创建实例对象
 */
var foo01 = new Foo("76433", 18)
var foo02 = new Foo("水逆信封", 18)

image-20241110193731532.png

绘图网址

支持私聊,有错误的话,积极指出,因为光是图形解的,不容易看的清除

练习方式: 3137536916@qq.com