经过一道面试题来学习原型/原型链-函数声明/函数表达式

例题

如题: html

答案解析

function A() {
    B = function () {console.log(10)}
    return this
};

A.B = function () {console.log(20)};

A.prototype.B = function () {console.log(30)}

var B = function () {console.log(40)}

function B() {console.log(50)}

A.B() // answer 20 【原型与原型链】
// 在`A`的原型对象中查找是否有`B`函数而且调用,这里并未执行`A`函数。
// A.B = function () {console.log(20)};
// 在A的原型对象中添加了`B`函数,中止查找,因此答案为 20

B() // answer 40 【函数表达式和函数声明】
// var B = function () {console.log(40)}
// function B() {console.log(50)}
// 同名 -> 函数提高会 > 变量提高
// 换言之 同名的函数表达式和函数声明同时存在时 老是执行表达式

A().B() // answer 10 【函数表达式和函数声明】
// A() 执行函数A ==> 1.变量B从新赋值函数 2.返回this(window)
// .B() 执行全局下的B函数 已经被从新赋值 因此输出10

B() // answer 10 
// 上面的代码执行过A函数了,此时全局下的B函数输出10

new A.B() // answer 20【函数表达式和函数声明】
// new 执行了 A.B = function () {console.log(20)};

new A().B() // answer 30
// new 执行构造函数 A ,全局变量 B 从新赋值函数10
// 此时A() 指针指向哪里? 
// 首先要知道 new 作了什么事?
// ==> 建立空对象objA objA.__proto__ = A.prototype
// .B() 在A的原型对象中查找 B; A.prototype 指向函数的原型对象
// A.prototype.B = function () {console.log(30)} 输出 30
复制代码

原型和原型链

prototype

每个函数都有一个 prototype 属性。git

function Foo() {}

Foo.prototype; // {constructor,__proto__}
复制代码

不管何时,只要建立了一个新函数,根据一组特定的规则为该函数建立一个prototype 属性,这个属性指向函数的原型对象。github

那么这个建立的原型对象是什么呢?express

{
    constructor: ƒ Foo(),
    __proto__: Object
}
复制代码

constructor 属性

每个原型对象都有一个 constructor 属性函数

建立了自定义的构造函数后,其原型对象只会默认取得 constructor 属性。这个属性解决了对象识别问题,便可以经过该属性判断出实例是由哪一个构造函数建立的。post

Foo.prototype.constructor === Foo; // true
复制代码

前面说了,原型对象只会默认取得 constructor 属性,那么原型对象的其余属性(好比:__proto__ )是这么来的呢,这就要说到 __proto__ 指针了。ui

proto

每个实例都有一个 __proto__ 指针,指向构造函数的原型对象。this

var foo = new Foo();
foo.__proto__ === Foo.prototype; //true
复制代码

上面提到的构造函数的原型对象它自己也是一个实例,因此在它内部会有一个__proto__ 指针。spa

构造函数(补充)

ECMAScript 中提供了构造函数来建立新对象。但构造函数自己就是一个函数,与普通函数没有任何区别,只不过为了区分,通常将其首字母大写,但这并非必须的。prototype

函数被 new 关键字调用时就是构造函数。

function f(name) {
    console.log("execute");
    this.name = name;
}

var k = new f("k"); // execute =====> 调用new
console.log(k); // {name: "k"}
var h = f("h"); // execute =====> 未调用new
console.log(h); // undefined
复制代码

从上面代码能够看出:

  • 首字母是否大写并不影响函数 f 做为构造函数使用,
  • 不使用 new 调用函数就是普通函数,直接执行内部代码,使用new,函数的角色就成为了构造函数,建立一个对象并返回。

对象由构造函数经过 new 创造对象的步骤

var obj = {}; // 建立一个空对象
obj.__proto__ = constructor.prototype;//添加__proto__属性,并指向构造函数的prototype 属性。
constructor.call(this); // 绑定this
return obj;
复制代码

new 关键字的内部实现机制:

  • 建立一个新对象;
  • 将构造函数的做用域赋值给新对象;
  • 执行构造函数中的代码;
  • 返回新对象

原型链

原型链的理论主要基于上述提到的构造函数、实例和原型的关系:

  • 每个构造函数都有一个原型对象
  • 原型对象都包含一个指向构造函数的 constructor 属性
  • 每个实例都包含一个指向原型对象的 __proto__ 指针 其中最最重要的是第三条,依赖这条关系,层层递进,就造成了实例与原型的链条。

接着上面的探索,构造函数的原型的原型是由 Object 生成的,那么 Object 的原型是由什么生成?而原型链的终点又是在哪?

Object.prototype.__proto__ // null
null.__proto__; // Uncaught TypeError: Cannot read property '__proto__' of null
// game over
复制代码

原型的终点是 null,由于 null 没有 proto 属性。

最后以一个例子来理解上面所谈到的原型与原型链

function Foo(){} // 构造函数 Foo
var foo = new Foo() // foo.__proto__ 指向 Foo.prototype
复制代码

函数声明、函数表达式

函数声明 function name(){}

函数声明是用指定的参数声明一个函数。一个被函数声明建立的函数是一个 Function 对象,具备 Function 对象的全部属性、方法和行为。

// 函数声明语法
function name([param[, param[, ... param]]]) { statements }
复制代码

函数表达式 var name = function(){}

在函数表达式中咱们能够忽略函数名称建立匿名函数,并将该匿名函数赋值给变量。

var add = function(a, b) {
    return a + b;  
};

add(2, 3) // 5
复制代码

固然, 也能够建立命名函数表达式 Named function expression:

var add = function func(a, b) {
    return a + b;  
};

add(2, 3) // 5
复制代码

命名函数表达式中函数名称只能做为函数体做用域内的局部变量,外部不可访问。

var a = function pp(v) {
    v++;
    if (v>3) {
        return v;
    } else {
        return pp(v);
    }
}

a(1); // 4
pp(1); // ReferenceError: pp is not defined
复制代码

函数声明提高

对于函数声明建立的函数,能够在本做用域内任意位置访问。

a(); // 1

function a() {
    return 1;  
}

a(); // 1
复制代码

而函数表达式不会。

console.log(a); // undefined (只是变量提高)
a(1); // TypeError: a is not a function

var a = function(v) {
    console.log(v);     
};
复制代码

函数提高和变量提高的疑惑分析

console.log(fn); // [Function: fn]
var fn = function () {
    console.log(1);
}

function fn() {
    console.log(2);
}

fn() // 1
复制代码

提高过程

// 函数提高
function fn() {
    console.log(2);
}

// 变量提高
var fn;

fn = function () {
    console.log(1);
}
fn() //最终输出1
复制代码

参考


相关文章
相关标签/搜索