JS原型、原型链到底是什么?

news/2024/11/17 23:51:07/

前言

在js的学习中,原型毫无疑问是一个难点,但也是一个不可忽视的重点。在前端面试中也是一个高频考题,在接下来的深入学习中,你会发现原型、原型链等知识点其实并不难。

1. “一切皆为对象”

JavaScript是一个面向(原型)对象的语言,对象是属性的集合,除了值类型 ”一切(引用类型)皆为对象“,判断一个变量是不是对象,值类型可以用typeof,引用类型用instanceof。

我们可以通过new来创建一个对象,但其实所有对象都是通过函数创建的,而函数也是对象。等等,这好像有点绕,不急,让我们先去了解prototype原型。

2. 原型

prototype

在JavaScript中,所有的函数默认都会拥有一个名为prototype的公有且不可枚举的属性,它会指向另一个对象:这个对象通常被称为函数的原型。简单来说,prototype就是函数的一个属性,这个prototype的属性值是一个对象(对象是属性的集合)。

function Foo() {}
var a = new Foo;
console.log(a.__proto__ === Foo.prototype); // true 

上面代码通过new Foo()创建a时,将a内部的隐式原型( __proto__ )链接到Foo.prototype所指的对象,即a.__proto__ === Foo.prototypea.__proto__ 指向了 Foo.prototype

隐式原型__proto__

我们现在知道每个 函数 都有一个prototype,那再介绍一个隐式原型, __proto__是每个 对象 都有的一个隐式原型,换句话说,这个奇怪的__proto__引用了内部的原型对象,实际上__proto__并不存在你正在使用的对象中,当获取a.__proto__时,实际上调用了a.__proto__(调用了getter函数),getter不在我们目前的讨论范围内了,我们只需要知道__proto__是我们用来获取对象内部原型的方法,跟es5的标准Object.getPrototypeOf(a)结果一样。

让我们梳理一下,我们可以通过new Foo()创建a,使a.__proto__指向创建该对象的函数的prototype

函数也是对象,函数也有__proto__?

Function

前面我们提到过所有对象都是通过函数创建的,而函数也是一种对象,那函数是谁创建的呢?答案是 Function :

function Foo(a){return a;
};
console.log(Foo(7)); // 7var Foo1 = new Function("a","return a");
console.log(Foo1(6)); // 6console.log(Foo.__proto__ === Function.prototype); // true 

我们可以通过new Function来创建一个函数,它跟我们平时创建的函数达到了一样的效果,这种方式不推荐去使用,现在我们还可以得出Foo.__proto__ === Function.prototype

除了Function,我们还要介绍一下 Object:

var obj = {};
console.log(obj.__proto__ === Object.prototype);  // true 

跟函数Foo是被Function创建的一样,obj本质上也是由 Object 创建的。

注意,前面我们说的 FunctionObject 都是函数,也是对象。也就是说, 函数由 Function 函数创建,而函数又是对象,对象由 Object 函数创建,然后函数又由 Function 函数创建…Foo.__proto__ === Function.prototype -->Object.__proto__ === Function.prototype–>Function.__proto__ === Function.prototype?怎么感觉进入了循环?我造我自己?用一个图来表示:

微信图片_20220502170639.jpg

由上图可以发现,这其实就是一个无限循环!Function 是一个函数,函数又是一种对象,也有__proto__属性。Function 这个函数就是被自身所创建,它的 __proto__ 指向了自身的prototype

说到这里你可能有点晕了,我们可以先放一放,接下去看一下原型和隐式原型之间关联在一起的意义。

3. 原型链

我们先来看一段代码:

function Foo() {}
Foo.prototype.a = 7;
Foo.prototype.sayHi = function(){console.log('Hello');
}
var bar = new Foo();
console.log(bar.a); // 7
bar.sayHi(); // Hello 

访问对象属性时,引擎实际上会调用内部的Get操作,这个操作会检查对象本身是否包含这个属性,如果没找到还会继续沿着__proto__向上查找直到尽头,这就是 原型链 ,所有普通的原型链最终都会指向内置的Object.prototype,它为null。 所以在上面代码,能从原型链上找到a属性。


借助原型链,Function函数的prototype中的一些方法和属性(call,apply,bindarguments…)可以在每一个函数中使用,因为函数由Function函数创建。对象也是如此,Object.prototype上的(toString, valueOf, isPrototypeOf…)也可以方便我们使用。 我们也可以在prototype中添加自定义属性。

继承?

创建的新对象可以使用另一个对象上的属性和函数, 原型链的机制很容易让我们联想到其他语言中的继承。继承意味着复制操作,但JavaScript并不会复制对象属性,JavaScript会在两个对象之间创建关联(prototype),意味着某些对象在找不到属性和方法引用时会把这个请求 委托 给另一个对象。 委托这个词能更准确地描述JavaScript中对象的关联机制。

4. ”构造函数“

function Foo() {}
console.log(Foo.prototype.constructor === Foo); // true
var bar = new Foo();
console.log(bar.constructor === Foo); // true 

每一个函数原型上(本例中Foo.prototype)默认有一个公有并且不可枚举的属性constructor,这个属性引用的是对象关联的函数(Foo),在上面的代码中,我们会把Foo这个函数看做是一个”构造函数“,因为我们看到它“构造”了这个对象。我们可能会习惯用constructor来去理解判断。

那能否通过bar.constructor === Foo 的结果来判断bar的”构造函数“是不是Foo呢。

function Foo() {}
Foo.prototype = {}; // 新的原型对象
console.log(Foo.prototype.constructor === Foo); // falsevar bar = new Foo();
console.log(bar.constructor === Foo); // false
console.log(bar.constructor === Object); // true 

通过上面的代码我们可以发现,我们看起来应该是Foo函数”构造“了bar,但事实上这里的bar.constructor并没有指向Foo,反而是指向了Object函数。

原因:

我们懂了原型链就会知道这并不奇怪,bar其实并没有constructor属性,bar会委托原型链上的Foo.prototype。 由于我们给Foo创建了一个新原型对象,所以Foo已经没有了默认的constructor属性,然后a会继续沿着原型链查找直到原型链的顶端Object.prototype,这个对象上有constructor属性(每一个函数原型上都默认有)。

所以bar.constructor并不是一个安全可靠的引用,有时会指向你意想不到的地方。

实际上Foo和其他函数没有什么区别,函数本身并不是构造函数,在JavaScript中对于”构造函数“最准确的解释是:所有带new的函数调用。

5. Object.create 和 new

在前文我们多次使用到了new,可以看出使用new发生构造函数调用时,会创建一个新对象,这个新对象会被执行原型连接。直接点解释就是这个新对象的隐式原型链接到”构造函数“的原型对象。

二者有什么联系:

Object.create( )是Object的内置方法,它也会创建一个新对象,跟new有什么区别呢?我们先来看Object.create( )的polyfill代码,它部分实现了Object.create( )。

if (!Object.create) {Object.create = function(o) {function F(){}F.prototype = o;return new F();};
} 

可以看到,这个代码使用一个一次性函数F,通过改写它的prototype属性使其指向想要关联的对象,我们使用下面这段代码来说明:执行这一行Object.create( )时加入polyfill代码,可以推出(var b = new F( ) ,F.prototype = foo),相当于b这个新对象的内部原型链接到foo。

var foo = {sayHi: function() {console.log("hello")}
};
var bar = Object.create(foo);
bar.sayHi();  // hello 

Object.create(),该新对象(b)的隐式原型指向现有对象(foo),这样不仅可以充分利用原型还避免了一些不必要的麻烦(比如constructor)。

6. 内省

通过内省找出对象的”祖先“(委托关联)

instanceof

a instanceof Foo:会判断在a的整条__proto__链中是否有Foo.prototype指向的对象,即之中是否有同一个对象。

isPrototype()

b.isPrototype©:会判断b是否出现在c的prototype链中。

function Foo() {}
// a的隐式原型 链接到Foo.prototype所指的对象
var a = new Foo();
console.log(a instanceof Foo); // true
console.log(Foo.prototype.isPrototypeOf(a)); // true// Bar的隐式原型 链接到Foo
var Bar = Object.create(Foo);
console.log(Foo.isPrototypeOf(Bar)); // true // a.prototype这个对象的隐式原型 链接到Foo.prototype
a.prototype = Object.create(Foo.prototype);
console.log(a.prototype instanceof Foo); // true
console.log(Foo.prototype.isPrototypeOf(a.prototype)); // true 

结语

我们可以从下图中看到十分复杂的关系图,但需要我们耐心去看去分析,可以从中收获很多。

微信图片_20220502184449.png


http://www.ppmy.cn/news/484824.html

相关文章

原型及原型链 ①

文章目录 原型及原型链函数对象constructor 构造函数new 操作符将构造函数当作函数构造函数的问题prototype 原型理解原型对象更简单的原型语法原型的动态性原生对象的原型原型对象的问题构造函数和原型结合构造函数和原型结合 __proto__原型链关卡 本文转:https://…

原型原型链深度解析

原型 原型是函数特有的,构造函数制造出对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。 我们先写一个构造函数 function Person() {} var person new Person(); person.name luoxi; console.log(person.name) // luoxi在这…

原型,原型链,原型的继承

原型的作用? 1.节省内存空间 2.实现数据共享(继承) 什么是原型? 任何一个函数都有propotype属性,它本身是一个对象,我们称之为原型 构造函数,实例化对象与原型之间的关系? 1.任何一个函数都有prototype属性,它本身是一个对象,我们称之为原型 2.构造函数也是函数,也都…

原型和原型链详细讲解

目录 一、构造函数 二、原型对象 三、原型链 四、函数也是一种对象 五、总结 一、构造函数 构造函数、普通函数区别,使用new关键字创建对象的函数叫构造函数。构造函数的首字母一般是大写,用以区分普通函数。 function Person(name, age) {this.nam…

硬核!原型和原型链详解

前言 我是歌谣 知其然知其所以然 人人都有一个大厂梦 希望通过自己的一个总结分享可以给予大家带来帮助和提升。 本期知识点 原型和原型链 目标 1理解原型和原型链 2理解构造函数 3理解构造函数 原型和原型链之间的关系 引用类型都是对象 基本数据类型和引用数据类型可以看下…

【原型和原型链】什么是原型和原型链

一、原型 ①所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象 ②所有函数都有一个prototype(原型)属性,属性值是一个普通的对象 ③所有引用类型的__proto__属性指向它构造函数的prototype var a [1,2,3]; a.__proto__ Array.pro…

原型链详解

什么是原型链 原型链就是顺着__proto__所在的一条链子,这样说可能不是很好理解,下面来看例子你就会理解。 需要了解 1.a.__proto__和 a.prototype都称做为a的原型。 2.Object.prototype是原型链的顶端。 3.如果 a是由b实例化出来的,则有关…

(二)原型和原型链

文章目录 一、原型和原型链1.构造函数2.为什么要一个原型对象?3.两个属性:prototype与__proto__4.原型链 二、new和Object.create的区别1.Object.create的使用2.二者区别 一、原型和原型链 在接触原型与原型链中,我产生了以下几个疑问&#…