模范爸爸|「干货满满」1.5w字初中级前端面试复习总结( 二 )

例子:
function foo(x, y) {console.log(x)var x = 10console.log(x)function x(){}console.log(x)}foo(20, 30)// 1. 创建AO对象AO {}// 2. 寻找形参和变量声明赋值为 undefinedAO {x: undefinedy: undefined}// 3. 实参形参相统一AO {x: 20y: 30}// 4. 函数声明提升AO {x: function x(){}y: 30}编译结束后代码开始执行 , 第一个 x 从 AO 中取值 , 输出是函数x;x 被赋值为 10 , 第二个 x 输出 10;函数x 已被声明提升 , 此处不会再赋值 x , 第三个 x 输出 10 。
作用域作用域能保证对有权访问的所有变量和函数的有序访问 , 是代码在运行期间查找变量的一种规则 。
函数作用域函数在运行时会创建属于自己的作用域 , 将内部的变量和函数定义“隐藏”起来 , 外部作用域无法访问包装函数内部的任何内容 。
块级作用域在ES6之前创建块级作用域 , 可以使用 with 或 try/catch 。 而在ES6引入 let 关键字后 , 让块级作用域声明变得更简单 。 let 关键字可以将变量绑定到所在的任意作用域中(通常是{...}内部) 。
{let num = 10}console.log(num) // ReferenceError: num is not defined参数作用域一旦设置了参数的默认值 , 函数进行声明初始化时 , 参数会形成一个单独的作用域 。 等到初始化结束 , 这个作用域就会消失 。 这种语法行为 , 在不设置参数默认值时 , 是不会出现的 。
let x = 1;function f(x, y = x) {console.log(y);}f(2) // 2参数y的默认值等于变量x 。 调用函数f时 , 参数形成一个单独的作用域 。 在这个作用域里面 , 默认值变量x指向第一个参数x , 而不是全局变量x , 所以输出是2 。
let x = 1;function foo(x, y = function() { x = 2; }) {x = 3;y();console.log(x);}foo() // 2x // 1y 的默认是一个匿名函数 , 匿名函数内的x指向同一个作用域的第一个参数x 。 函数foo的内部变量x就指向第一个参数x , 与匿名函数内部的x是一致的 。 y函数执行对参数x重新赋值 , 最后输出的就是2 , 而外层的全局变量x依然不受影响 。
闭包闭包的本质就是作用域问题 。 当函数可以记住并访问所在作用域 , 且该函数在所处作用域之外被调用时 , 就会产生闭包 。
简单点说 , 一个函数内引用着所在作用域的变量 , 并且它被保存到其他作用域执行 , 引用变量的作用域并没有消失 , 而是跟着这个函数 。 当这个函数执行时 , 就可以通过作用域链查找到变量 。
let barfunction foo() {let a = 10// 函数被保存到了外部bar = function () {// 引用着不是当前作用域的变量aconsole.log(a)}}foo()// bar函数不是在本身所处的作用域执行bar() // 10优点:私有变量或方法、缓存
缺点:闭包让作用域链得不到释放 , 会导致内存泄漏
原型链JavaScript 中的对象有一个特殊的内置属性 prototype(原型) , 它是对于其他对象的引用 。 当查找一个变量时 , 会优先在本身的对象上查找 , 如果找不到就会去该对象的 prototype 上查找 , 以此类推 , 最终以 Object.prototype 为终点 。 多个 prototype 连接在一起被称为原型链 。
原型继承原型继承的方法有很多种 , 这里不会全部提及 , 只记录两种常用的方法 。
圣杯模式function inherit(Target, Origin){function F() {};F.prototype = Origin.prototype;Target.prototype = new F();// 还原 constuctorTarget.prototype.constuctor = Target;// 记录继承自谁Target.prototype.uber = Origin.prototype; }圣杯模式的好处在于 , 使用中间对象隔离 , 子级添加属性时 , 都会加在这个对象里面 , 不会对父级产生影响 。 而查找属性是沿着 __proto__ 查找 , 可以顺利查找到父级的属性 , 实现继承 。


推荐阅读