js函数与原型链

简介: js函数与原型链

JavaScript 函数的特性与原型链

  • JavaScript 中的函数大致有以下三种使用方式:

一、作为普通函数来使用

    // 定义函数
    function foo() {}
    // 调用函数
    foo();

二、作为构造函数来使用

  • 当一个函数被用来创建新对象的时候,就把它叫做:构造函数。
    // 按照惯例,作为构造函数的函数名首字母需要大写
    function Foo() {}
    const obj = new Foo();

这时对于对象 obj 来说,函数 Foo 就叫做它的构造函数。

三、作为对象来使用

访问对象的属性可以使用点操作符和中括号来访问

    function foo() {}
    foo.name = 'tom';
    foo['age'] = 20;

以上这三种使用方式中,以普通函数来调用的方式大家都懂,这里不再赘述。本文主要讲解的就是当一个函数被作为构造函数来使用和被作为对象来使用的时候,分别是什么样的,以及它们之间与原型链的关系是什么样的

预留问题

在讲接下来的内容之前,我们先来看几个问题:

    // 首先,定义一个函数,将会作为构造函数
    function Foo() {}

    // 实例化出来一个对象
    const obj = new Foo();

    // 在 Object 的原型上定义一个属性:objProp
    Object.prototype.objProp = '我是 Object 原型上的属性';

    // 在 Function 的原型上定义一个属性:funcProp
    Function.prototype.funcProp = '我是 Function 原型上的属性';

    // 你预想一下,以下这些分别会输出什么?
    console.log(obj.objProp) // ?
    console.log(obj.funcProp) // ?

    console.log(Foo.objProp) // ?
    console.log(Foo.funcProp) // ?

    console.log(Object.objProp) // ?
    console.log(Object.funcProp) // ?

    console.log(Function.objProp) // ?
    console.log(Function.funcProp) // ?

    console.log(Array.objProp) // ?
    console.log(Array.funcProp) // ?

构造函数

使用new来构造一个韩式
当我们使用 new 操作符来从构造函数创建对象的时候,会经历以下几个步骤:

创建一个空对象:{}。
把这个空对象的原型链 proto 指向构造函数的原型对象。
把新对象作为上下文来调用构造函数,即绑定 this,这样新对象才能访问到构造函数中的属性。
如果该函数没有返回对象,则返回这个新对象。

    // 新建一个构造函数
    function Foo() {}
    // 使用 new 操作符实例化一个对象
    const obj_foo = new Foo()
    // ========创建对象的过程:
    // 1. 创建一个空对象:`{}`。
    const obj = {};
    // 2. 原型链的链接过程
    obj.__proto__ = Foo.prototype;
    // 3. 把新对象作为函数的上下文,即绑定 this
    Foo.apply(obj, arguments);
    // 4. 因为 Foo 没有返回对象,所以就返回这个新对象
    return obj;
    // ========END
    // 这时 obj_foo 就是返回出来的新对象了

proto 是什么?

首先你要记住:只有对象才会有 proro 这个属性。它是 js 得以实现原型链的关键,因为它会指向构造函数的原型对象,即 prototype 属性上的对象,而构造函数原型对象上的 proto 又指向上一级,即:构造函数.prototype.__proto__ -> 上一级构造函数.prototype,以此类推层层往上,就形成了我们所说的原型链。如果不理解,请继续看下文:

prototype 是什么?

前面一直提到 构造函数.prototype,这个属性是函数特有的一个属性,它是构造函数的原型对象,所有由它构造出来的对象都会从这个原型对象上继承属性和方法(参考 MDN:JavaScript 是一种基于原型的语言,每个对象都拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性)。就像前面的例子:const obj_foo = new Foo(),obj_foo 的原型对象就是构造它的函数 Foo 的 prototype,obj_foo 从 Foo.prototype 继承了方法和属性。
又因为 构造函数.prototype 是一个对象,所以它就会有 proto 这个属性,说明原型对象也是由一个构造函数创建出来的,即:构造函数.prototype.__proto__ -> 上一级构造函数.prototype。
这下你能理解 __proto__、prototype 和原型链之间的关系了吧,正因为有了它们俩 JavaScript 才得以实现原型链。

特殊的构造函数

先来回顾一下 JavaScript 中的一些内建构造函数吧,比如:Object、Function、Number、Array、String等。
既然是构造函数,就可以用来创建对象,比如:

    var a = new Number('123'); // a === 123 is false
    // 因为 Number.prototype 上有 toString() 方法,于是 a 也就继承了该方法:
    a.toString(); // a === '123' is true
    var b = new String(123); // b === '123' is false
    a instanceof Number; // is true
    b instanceof String; // is true

有了上述印象,接下来就跟大家讲解其中最核心最特殊的两个构造函数:Object 和 Function。
Object 构造函数
用来创建对象,例如:

const obj = new Object();

Object 除了是一个构造函数可以用来创建对象以外,它还拥有一个很硬核的能力:派生所有的其它构造函数。
这里用了“派生”二字,在理解上是把它当做一个基类或者说父类来看待的。
也就是:所有的其它构造函数都是由 Object 这个构造函数派生出来的(子类)。
用代码解释一下(注:JavaScript 中没有明确的类的概念):

// 父类 Object
class Object {}
// 子类 MyFunction
class MyFunction extends Object {}

正因为是由 Object 构造出来的,那么构造函数的原型链就指向了 Object 的原型对象(参考 new 的第二步过程可以加以理解):
所有构造函数.prototype.__proto__ === Object.prototype
重点
因为 Function 也是一个构造函数,那么就可以推导出:

Function.prototype.__proto__ === Object.prototype
Function 构造函数
用来创建函数对象,例如:
const sum = new Function('a', 'b', 'return a + b');
sum instanceof Function; // true

在这里把 sum 叫函数对象,是因为它确实是由 Function 构造出来的对象,但是它跟我们平常定义一个 sum 函数是一样的,这就是我当初的迷惑点所在,即函数有多种表示形态,可以是普通函数,可以是构造函数也可以是函数对象。
我们不难得出:所有的函数对象都是由 Function 构造出来的
证明:sum.__proto__ === Function.prototype
重点
进一步可以推导出:Object.__proto__ === Function.prototype
阶段小结

函数有三种形态:普通函数、构造函数、函数对象;

当使用 new 操作符调用构造函数,会创建一个新对象;
proto 只有对象才有这个属性,它指向对象的构造函数的原型对象;
prototype 是构造函数的原型对象;
所有的构造函数都是由 Object 创建出来的;
所有的函数对象都是由 Function 创建出来的。

函数是如何确定形态的

前面我们讲了构造函数、函数对象这些东西,那么函数是如何确定最终形态的呢?答案是:在使用的时候。
使用 new 来调用它,那么它就是一个可以创建对象的构造函数,如果使用点或者中括号来调用它,那么它就又变成了一个对象(注:排除 prototype 属性)。
我在前面标了两个重点公式:

// 1
Function.prototype.__proto__ === Object.prototype
// 2
Object.__proto__ === Function.prototype

再次解释一下:
1、看到 Function.prototype 就说明 Function 此时是被当做构造函数来使用的,前面说过所有构造函数都是由 Object 构造出来的。
2、看到 Object.__proto__ 就说明 Object 此时是被当做对象来使用的,前面说过所有函数对象都是由 Function 创建出来的。
Object 和 Function 之间的关系非常特殊,根据不同的形态可以互生。

解决预留问题

这下我们就可以来解决预留问题了。

// 首先,定义一个函数,将会作为构造函数
function Foo() {}

// 实例化出来一个对象
const obj = new Foo();

// 在 Object 的原型上定义一个属性:objProp
Object.prototype.objProp = '我是 Object 原型上的属性';

// 在 Function 的原型上定义一个属性:funcProp
Function.prototype.funcProp = '我是 Function 原型上的属性';
1、console.log(obj.objProp) // ?

答案:

因为对象的原型链指向构造函数的原型对象,所以:obj.__proto__ -> Foo.prototype,
因为所有构造函数都是由 Object 派生出来的,所以:Foo.prototype.__proto__ -> Object.prototype
因为 Object.prototype 上有 objProp 这个属性,所以 obj.objProp === '我是 Object 原型上的属性'

2、console.log(obj.funcProp) // ?

答案:

因为对象的原型链指向构造函数的原型对象,所以:obj.__proto__ -> Foo.prototype,
因为所有构造函数都是由 Object 派生出来的,所以:Foo.prototype.__proto__ -> Object.prototype
由于在 Object.prototype 上找不到 funcProp 属性,根据原型链继续找:Object.prototype.__proto__ -> null
找不到,返回 undefined

3、console.log(Foo.objProp) // ?

答案:

Foo.objProp 是把 Foo 当做对象来使用的,此时它是一个函数对象,所以它是由 Function 创建出来的:Foo.__proto__ -> Function.prototype
由于在 Function.prototype 上找不到 objProp 属性,根据原型链继续找:Function.prototype.__proto__ -> Object.prototype
在 Object.prototype 上找到了 objProp 属性,所以 Foo.objProp === '我是 Object 原型上的属性'

4、console.log(Foo.funcProp) // ?

答案:

Foo.funcProp 是把 Foo 当做对象来使用的,此时它是一个函数对象,所以它是由 Function 创建出来的:Foo.__proto__ -> Function.prototype
在 Function.prototype 上找到了 funcProp 属性,所以 Foo.funcProp === '我是 Function 原型上的属性'

5、console.log(Object.objProp) // ?

答案:

Object.objProp 是把 Object 当做对象来使用的,此时它是一个函数对象,所以它是由 Function 创建出来的:Object.__proto__ -> Function.prototype
在 Function.prototype 上找不到 objProp 属性,根据原型链继续找:Function.prototype.__proto__ -> Object.prototype
在 Object.prototype 上找到了 objProp 属性,所以 Object.objProp === '我是 Object 原型上的属性'

6、console.log(Object.funcProp) // ?

答案:

Object.funcProp 是把 Object 当做对象来使用,此时它是一个函数对象,所以它是由 Function 创建出来的:Object.__proto__ -> Function.prototype
在 Function.prototype 上找到了 funcProp 属性,所以 Object.funcProp === '我是 Function 原型上的属性'

7、console.log(Function.objProp) // ?
为了避免混淆,Function 表示对象, %Function 表示构造函数

答案:

Function.objProp 是把 Function 当做对象来使用的,此时它是一个函数对象,所以它是由 %Function 创建出来的:Function.__proto__ -> %Function.prototype
在 %Function.prototype 上找不到 objProp 属性,根据原型链继续找:%Function.prototype.__proto__ -> Object.prototype
在 Object.prototype 上找到了 objProp 属性,所以 Function.objProp === '我是 Object 原型上的属性'

8、console.log(Function.funcProp) // ?

答案:

Function.funcProp 是把 Function 当做对象来使用的,此时它是一个函数对象,所以它是由 $Function 创建出来的:Function.__proto__ -> %Function.prototype
在 %Function.prototype 上找到了 funcProp 属性,所以 Function.funcProp === '我是 Function 原型上的属性'

9、console.log(Array.objProp) // ?

自己尝试着分析一下吧,欢迎在评论区给出

10、console.log(Array.funcProp) // ?
相关文章
|
2月前
|
JavaScript 前端开发 开发者
理解JavaScript中的原型链:基础与实践
【10月更文挑战第8天】理解JavaScript中的原型链:基础与实践
|
1月前
|
JavaScript 前端开发 Java
[JS]同事:这次就算了,下班回去赶紧补补内置函数,再犯肯定被主管骂
本文介绍了JavaScript中常用的函数和方法,包括通用函数、Global对象函数以及数组相关函数。详细列出了每个函数的参数、返回值及使用说明,并提供了示例代码。文章强调了函数的学习应结合源码和实践,适合JavaScript初学者和进阶开发者参考。
43 2
[JS]同事:这次就算了,下班回去赶紧补补内置函数,再犯肯定被主管骂
|
1月前
|
JavaScript 前端开发
JavaScript 原型链的实现原理是什么?
JavaScript 原型链的实现原理是通过构造函数的`prototype`属性、对象的`__proto__`属性以及属性查找机制等相互配合,构建了一个从对象到`Object.prototype`的链式结构,实现了对象之间的继承、属性共享和动态扩展等功能,为 JavaScript 的面向对象编程提供了强大的支持。
|
1月前
|
JavaScript 前端开发
原型链在 JavaScript 中的作用是什么?
原型链是 JavaScript 中实现面向对象编程的重要机制之一,它为代码的组织、复用、扩展和多态性提供了强大的支持,使得 JavaScript 能够以简洁而灵活的方式构建复杂的应用程序。深入理解和熟练运用原型链,对于提升 JavaScript 编程能力和开发高质量的应用具有重要意义。
|
1月前
|
前端开发 JavaScript 开发者
除了 Generator 函数,还有哪些 JavaScript 异步编程解决方案?
【10月更文挑战第30天】开发者可以根据具体的项目情况选择合适的方式来处理异步操作,以实现高效、可读和易于维护的代码。
|
1月前
|
JavaScript 前端开发
如何使用原型链继承实现 JavaScript 继承?
【10月更文挑战第22天】使用原型链继承可以实现JavaScript中的继承关系,但需要注意其共享性、查找效率以及参数传递等问题,根据具体的应用场景合理地选择和使用继承方式,以满足代码的复用性和可维护性要求。
|
2月前
|
JavaScript 前端开发 开发者
探索JavaScript原型链:深入理解与实战应用
【10月更文挑战第21天】探索JavaScript原型链:深入理解与实战应用
37 1
|
2月前
|
JavaScript 前端开发
JavaScript 函数语法
JavaScript 函数是使用 `function` 关键词定义的代码块,可在调用时执行特定任务。函数可以无参或带参,参数用于传递值并在函数内部使用。函数调用可在事件触发时进行,如用户点击按钮。JavaScript 对大小写敏感,函数名和关键词必须严格匹配。示例中展示了如何通过不同参数调用函数以生成不同的输出。
|
2月前
|
存储 JavaScript 前端开发
JS函数提升 变量提升
【10月更文挑战第6天】函数提升和变量提升是 JavaScript 语言的重要特性,但它们也可能带来一些困惑和潜在的问题。通过深入理解和掌握它们的原理和表现,开发者可以更好地编写和维护 JavaScript 代码,避免因不了解这些机制而导致的错误和不一致。同时,不断提高对执行上下文等相关概念的认识,将有助于提升对 JavaScript 语言的整体理解和运用能力。
|
2月前
|
JavaScript 前端开发
js教程——函数
js教程——函数
48 4