js为啥要有作用域链,原型链?

149 阅读2分钟

这么多年来,大家都会出一些,关于作用域、作用域链、 原型对象、 原型链相关的问题。 那么大家有没有想过, javascript为啥要有这些东西? 有人会说,为了函数编程, 为了继承,为了面向对象... ; 我也觉得他们说的都对,但是没有找到它的本质?

作用域链、原型链的本质就是为了实现JavaScript的局部变量

作用域链

在js中, 一个变量是如何找到它的值? 答案可以理解为就近原则.

debugger;
var a  = 123
function foo() {
    console.log(a) // undefined
    var a = 666
    console.log(a) // 666
}
foo()
console.log(a) // 123

1.png

作用域

那么你觉得哪些手段能产生作用域呢? 大家动动手看一看?

  1. 函数作用域? (√)
  2. 大括号?
  3. try catch ?
  4. eval?
  5. with? (√)
// 大括号
debugger;
var a = 1
{
    var a  = 2
    console.log(a) // 2
}
console.log(a) // 2
// try catch
debugger;
var a = 2
try {
    var a = 3
    console.log(a) // 3
    throw Error()
} catch (e) {
    var a = 4
    console.log(a) // 4
}
console.log(a) // 4
// eval
debugger;
function foo(str, a) {
    eval(str)
    console.log(a, b)
}
var b = 2
foo('var b = 3;', 1) // 1,3

通过chrome调试工具可以发现eval其实并不会产生一个作用域, 而是重新启动了个虚拟机,然后把它放置在当前的作用域里

debugger;
var obj = {a:1, b:2}
with(obj) {
    a = 'a'
    b = 'b'
}

with可以产生一个With Block的作用域

闭包

闭包是啥? 函数就是保留了之前的作用域,作用域你可以理解为是一个对象。那么变量就沿着这个作用域链进行访问

debugger;
var a = -1
function foo() {
    var a = 0
    return function (str) {
        a++
        console.log(a)
    }
}
var b = foo()
b() // 1
b() // 2
console.log(a) // -1

2.png

let,const

debugger;
let a = 'let'
const b = 'const'
console.log(window.a, window.b) // undefined undefined
console.log(a, b) // let const

3.png

debugger
var arr = [];
for (var i = 0; i < 3; i++) {
    arr[i] = function () {
      console.log(i);
    };
}
arr.forEach(fn=>fn()) // 3 3 3;

此时访问的是都是window.i

debugger
var arr = [];
for (let i = 0; i < 3; i++) {
    arr[i] = function () {
      console.log(i);
    };
}
arr.forEach(fn=>fn()) // 0 1 2;

4.png

看来只有function能够保存之前产生的作用域, 保存了就会产生闭包; 不愧是一等公民!

箭头函数

debugger;
var a = 1
var foo = () => {
    console.log(a) // undefined
    var a = 666 
    console.log(a) // 666
}
foo()
console.log(a) // 1

原型链

debugger
function A(name = '') {
    if (name) {
        this.foo = name
    }
}
A.prototype.foo = 'foo'
var a = new A('a')
var b = new A()
console.log(a.foo) // a
console.log(b.foo) // 'foo'

[对象.属性] 是不是跟作用域链类似; 验证原型链去查找局部变量?

小结

  • js变量查找的规则就是由近及远
  • js局部变量 靠着两条链子, 作用域链(function)、原型链(object)
  • 能够产生作用域的有 函数作用域 , let/constwith [with一般禁用]
  • 闭包就是保留了之前的作用域, 所以每次都可以访问到

参考

  • 你不知道的JavaScript系列