原型与原型链

516 阅读4分钟

原型与原型链

原型与原型链

文章结构

  • 原型与原型链
    • 原型
    • 原型链
    • 原型链中的this
  • 作用域与闭包
    • 执行上下文
    • 作用域
    • 作用域链
    • 闭包
  • 面试题

原型与原型链

下面几条必须记住:

  • 所有的引用类型(数组,函数,对象),都具有对象特性,既可以自由扩展属性。
  • 所有的引用类型(数组,函数,对象),都具有__proto__属性,属性是一个普通的对象。
  • 所有的函数,都具有prototype,属性是一个普通的对象。
  • 所有的引用类型(数组,函数,对象),其__proto__属性都指向它的构造函数的prototype属性值。即:obj.__proto__ === Object.prototype
// 要点一:自由扩展属性
var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {}
fn.a = 100;

// 要点二:__proto__
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);

// 要点三:函数有 prototype
console.log(fn.prototype)

// 要点四:引用类型的 __proto__ 属性值指向它的构造函数的 prototype 属性值
console.log(obj.__proto__ === Object.prototype)

原型

当试图找到一个对象的某个属性时,如果该对象没有这个属性,那么就会到它的__proto__(即它的构造函数的prototype)去寻找。如果它的__proto__还是没有则继续在f.proto.proto(也即F.prototype.proto)上寻找。如果直到最上层也没有找到,则返回undefined

// 构造函数
function Foo(name, age) {
    this.name = name
}
Foo.prototype.alertName = function () {
    alert(this.name)
}
// 创建示例
var f = new Foo('zhangsan')
// 测试
f.alertName()

执行f.alertName()时,因为该对象没有alertName()方法,所以就会到它的__proto__(即Foo.prototype)上去寻找。此时找到了,调用该原型链上的alertName()方法。没有找到则继续查找。

原型链

上述找的过程,是一个链式的结构,叫做“原型链”。所有原型链的最上层都是null(即Object.prototype.proto === null)。

原型链中的this

所有从原型链中得到的方法,在this执行时,都指向了当前触发这个方法的对象。

作用域与闭包

执行上下文

在一段javsscript脚本执行之前,要先解析代码,解析的时候,会创建一个全局执行上下文环境,先把即将执行代码中的变量和函数的声明拿出来存放在这个环境中:变量暂时赋值为undefined,函数则声明好可以使用。这一步完成之后,再正式开始执行代码。

console.log(a)
var a = 100

fn('zhangsan')
function fn(name) {
    age = 20
    console.log(name, age)
    var age
}

console.log(b);
// Uncaught ReferenceError: b is not defined
b = 100;

上述代码执行过程:

  1. 创建全局执行环境
  2. 申明变量a(此时变量a赋值为undefined)、函数fn
  3. 开始执行代码: console.log(a) // undefinedfn('zhangsan') // zhangsan 20console.log(b) // 报错,因为全局执行环境中没有找到关于b的申明

一个函数在执行过程中,会创建函数执行上下文环境,相对于全局执行上下文环境,在函数执行上下文环境中,增加了this,arguments和函数的参数。

this

this在执行时才能确定其值,定义的时候不能确认。

?call apply bind的用法及手写实现。

作用域

在es6之前,javascript中没有块级作用域的概念。例如:

if (true) {
    var name = 'tom'
}
console.log(name) // tom

作用域就是一个独立的地盘,让变量不会泄露、暴露出去。JS 没有块级作用域,只有全局作用域和函数作用域。

var a = 100
function fn() {
    var a = 200
    console.log('fn', a)
}
console.log('global', a)
fn()

这就是为何 jQuery、Zepto 等库的源码,所有的代码都会放在(function(){....})()中。因为放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。这是函数作用域的一个体现。

ES6 中开始加入了块级作用域,使用let定义变量即可。

if (true) {
    let name = 'tom'
}
console.log(name) // 报错,因为在全局作用域中没有name的声明。

作用域链

执行某个函数作用域中的一段代码时,当一个变量的申明没有在当前作用域中找到,会一层一层向上级查找,这一层一层的关系,就是作用域链。若在全局作用域中还没有找到,才会报错。

var a = 100
function fn() {
    var b = 200
    console.log(a)
    console.log(b)
}
fn()

上述代码中,console.log(a)要得到a变量,但是在当前的作用域中没有定义a变量。像这种在当前作用域中没有定义但在作用域链中定义的变量,就是自由变量

自由变量将在作用域链中寻找,但是依据的是函数定义时候的作用域链,而不是执行时候的。

闭包

闭包主要有两个执行场景:

  • 函数作为返回值返回
  • 函数作为参数传递
function F1() {
    var a = 100
    return function () {
        console.log(a)
    }
}
var f1 = F1()
var a = 200
f1() // 100
function F1() {
    var a = 100
    return function () {
        console.log(a)
    }
}
function F2(f1) {
    var a = 200
    console.log(f1())
}
var f1 = F1()
F2(f1) // 100

面试题