JavaScript 实践+理论(总结篇):作用域、闭包、this、对象原型(上)

简介: JavaScript 实践+理论(总结篇):作用域、闭包、this、对象原型

JavaScript 实践+理论(总结篇):作用域、闭包、this、对象原型

作用域与闭包

第一章 作用域是什么

  • • 作用域:根据标识符查找变量的一套规则。
  • • 嵌套作用域:从当前作用域开始查找变量,如果找不到就向上一层继续查找,直到找到最外层的全局作用域为止。
  • • 严格模式与非严格模式下引擎查找规则:
  • • 严格模式:
  • • 非严格模式:
  1. 1. 引擎执行 RHS 时若找不到该标识符,会抛出 ReferenceError
  2. 2. 引擎执行 LHS 时若找不到该标识符,会隐式地在全局作用域中创建一个该名称的变量,并将其返回给引擎。
  3. 1. 在 use strict 模式下禁止自动或隐式地创建全局变量,所以在引擎执行 LHS 时,不会再隐式地创建一个全局变量,而是直接抛出一个 ReferenceError
  4. 2. 在该模式下,RHS 找到一个变量当对这个变量进行不合规的操作时会抛出一个 TypeError, 而 ReferenceError 代表着在作用域查找或判断失败,TypeError 代表作用域查找成功了,但对该变量的操作不合规。
  • • 引擎的查找规则:
  1. 1. LHS: 赋值操作的目标
  2. 2. RHS: 赋值操作的源头

第二章 词法作用域

  • • 作用域查找规则:从当前所处作用域最内部开始,逐级向上查找,直到找到第一个匹配的标识符为止。并且词法作用域只会查找一级标识符,如果 foo.bar.baz,词法作用域只会试图查找 foo 标识符,然后再分别访问 bar 和 baz。
  • • 函数不论是在哪里被调用,或如何被调用,它的词法作用域都是由被声明时所处的位置决定。
  • • 非严格模式下, eval(...) 中的语句会修改 eval(...) 所处的词法作用域。
  • • 严格模式下, eval(...) 在运行时有自己词法作用域,不会修改所处作用域。
  • • with(...) 会将当前对象的引用当做作用域来处理,将对象中的属性当做作用域中的标识符来处理,从而创建一个新的词法作用域。

附录 A 动态作用域

  • • 作用域是基于调用栈的,而不是代码中的作用域嵌套的。
  • • 动态作用域是在运行时确定的
  • • 词法作用域关注函数从何处声明
  • • 动态作用域关注函数从何处调用

第三章 函数作用域和块作用域

  • • 如何区分函数声明和函数表达式:如果 function 为声明中的第一个关键字,那它就是一个函数声明,否则就是一个函数表达式。
  • • IIFE(立即执行函数表达式),第一个() 将函数变成表达式,第二个() 将执行这个函数。且第二个 () 可放在第一个 () 内最后位置,且含义相同。
  • • 在 IIFE 中可在第二个 () 中传递参数,在第一个 () 中的形参就是第二个 () 所传进去的参数。
  • • var 声明符写在哪里都是一样的,因为它会变量提升。
  • • let 声明符声明的变量和函数不会被提升,何为提升,就是在代码执行时是否有被声明过,如果没有声明过则直接抛出错误。

第四章 提升

  1. 1. 先有鸡(声明),再有蛋(赋值)
  2. 2. 如 var a = 2; 这段声明代码 JavaScript 引擎会将他们分为 var aa = 2; 两个单独的声明来处理,第一个是在编译阶段所执行,第二个是在执行阶段所执行。
  3. 3. 重复定义的函数声明,后面的会覆盖前面的。
  4. 4. 函数声明会被提升,而函数表达式不会被提升
  5. 5. 只有函数本身会被提升, 而函数表达式在内的赋值操作并不会被提升。

第五章 作用域闭包

  1. 1. 何为闭包:当函数可以记住并访问所在的词法作用域时,即使函数在当前词法作用域之外执行,这时就会产生闭包。
  2. 2. 严格意义上来说,一个函数返回另一个函数。

3. 空的 IIFE 并不是闭包,虽然通过 IIFE 改造有用了更多的词法作用域,但在 IIFE 中的所创建的作用域是封闭起来的。只能通过从外传入一个参数到 IIFE 中被使用时,才是闭包。

for(var = 1 ; i <= 5; i++){
    (function() {
        var j = i;
        setTimeout(function timer() {
            console.log(j);
        }, j * 1000);
    })();
}
// 再次改进后
for(var = 1 ; i <= 5; i++){
    (function(j) {
        setTimeout(function timer() {
            console.log(j);
        }, j * 1000);
    })(i);
}

this 与对象原型


第一章 关于 this

  1. 1. this 既不指向函数自身也不指向函数的词法作用域
  2. 2. this 是在函数被调用时发生的绑定关系,它指向哪里完全取决于函数在哪里被调用

第二章 this 全面解析

  • • 判断 this 指向的四种规则:
  1. 1. 是否在 new 中调用(new 调用), this 指向新创建的对象
function Foo() {
    // do something
}
let f = new Foo();
// call()
function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
};
foo.call(obj); // 2
// apply()
function foo(something) {
    console.log(this.a, something);
    return this.a + something;
}
var obj = {
    a: 2,
};
var bar = function () {
    return foo.apply(obj, arguments);
};
var b = bar(3); // 2 3
console.log(b); // 5
// bind()
function foo(something) {
    this.a = something;
}
var obj1 = {};
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2
var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3
// eg1:
function foo() {
    console.log(this.a); // 2
}
var obj = {
    a: 2,
    foo: foo,
};
obj.foo();
// eg2:
function foo() {
    console.log(this.a);
}
var obj2 = {
    a: 42,
    foo: foo,
};
var obj1 = {
    a: 2,
    obj2: obj2,
};
obj1.obj2.foo(); // 42
function foo() {
    console.log(this.a);
}
var a = 2;
foo(); // 2
// 严格模式下的位置
function foo() {
    'use strict';
    console.log(this.a);
}
var a = 2;
foo(); // Type: this is undefined
function foo() {
    console.log(this.a);
}
var a = 2;
(function () {
    'use strict';
    foo(); // 2
});
  1. 1. 如果都不是,则是默认绑定,在严格模式下,this 指向 undefined。非严格模式下, this 指向全局对象。
  2. 1. 是否在某个对象中调用(隐式绑定), this 指向绑定对象的上下文
  3. 1. 是否通过 call, apply(显示绑定), this 指向绑定的对象
  1. 2. 箭头函数不会使用上述四条规则,而是根据当前的词法作用域来决定 this 的,箭头函数会继承外层函数的 this。
  2. 3. 注意:对于默认绑定来说,决定 this 绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式,this 会被绑定到 undefined, 否则 this 会绑定到全局对象。
  3. 4. 优先级问题
  • • 显式绑定:call()、apply()。(硬绑定也是显式绑定的其中一种: bind())
  • • new 绑定: new Foo()
  • • 隐式绑定: obj.foo();
  • • 默认绑定: foo();
  • 排序:显式绑定 > new 绑定 > 隐式绑定 > 默认绑定
相关文章
|
1月前
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
1月前
|
JavaScript 前端开发
js的作用域作用域链
【10月更文挑战第29天】理解JavaScript的作用域和作用域链对于正确理解变量的访问和生命周期、避免变量命名冲突以及编写高质量的JavaScript代码都具有重要意义。在实际开发中,需要合理地利用作用域和作用域链来组织代码结构,提高代码的可读性和可维护性。
|
1月前
|
Web App开发 JavaScript 前端开发
如何确保 Math 对象的方法在不同的 JavaScript 环境中具有一致的精度?
【10月更文挑战第29天】通过遵循标准和最佳实践、采用固定精度计算、进行全面的测试与验证、避免隐式类型转换以及持续关注和更新等方法,可以在很大程度上确保Math对象的方法在不同的JavaScript环境中具有一致的精度,从而提高代码的可靠性和可移植性。
|
9天前
|
存储 网络架构
Next.js 实战 (四):i18n 国际化的最优方案实践
这篇文章介绍了Next.js国际化方案,作者对比了网上常见的方案并提出了自己的需求:不破坏应用程序的目录结构和路由。文章推荐使用next-intl库来实现国际化,并提供了详细的安装步骤和代码示例。作者实现了国际化切换时不改变路由,并把当前语言的key存储到浏览器cookie中,使得刷新浏览器后语言不会失效。最后,文章总结了这种国际化方案的优势,并提供Github仓库链接供读者参考。
|
28天前
|
JSON 前端开发 JavaScript
JavaScript中对象的数据拷贝
本文介绍了JavaScript中对象数据拷贝的问题及解决方案。作者首先解释了对象赋值时地址共享导致的值同步变化现象,随后提供了五种解决方法:手动复制、`Object.assign`、扩展运算符、`JSON.stringify`与`JSON.parse`组合以及自定义深拷贝函数。每种方法都有其适用场景和局限性,文章最后鼓励读者关注作者以获取更多前端知识分享。
18 1
JavaScript中对象的数据拷贝
|
28天前
|
JavaScript 前端开发
JavaScript中的原型 保姆级文章一文搞懂
本文详细解析了JavaScript中的原型概念,从构造函数、原型对象、`__proto__`属性、`constructor`属性到原型链,层层递进地解释了JavaScript如何通过原型实现继承机制。适合初学者深入理解JS面向对象编程的核心原理。
25 1
JavaScript中的原型 保姆级文章一文搞懂
|
1月前
|
自然语言处理 JavaScript 前端开发
[JS]作用域的“生产者”——词法作用域
本文介绍了JavaScript中的作用域模型与作用域,包括词法作用域和动态作用域的区别,以及全局作用域、函数作用域和块级作用域的特点。通过具体示例详细解析了变量提升、块级作用域中的暂时性死区等问题,并探讨了如何在循环中使用`var`和`let`的不同效果。最后,介绍了两种可以“欺骗”词法作用域的方法:`eval(str)`和`with(obj)`。文章结合了多位博主的总结,帮助读者更快速、便捷地掌握这些知识点。
35 2
[JS]作用域的“生产者”——词法作用域
|
25天前
|
缓存 监控 JavaScript
Vue.js 框架下的性能优化策略与实践
Vue.js 框架下的性能优化策略与实践
|
26天前
|
缓存 负载均衡 JavaScript
构建高效后端服务:Node.js与Express框架实践
在数字化时代的浪潮中,后端服务的重要性不言而喻。本文将通过深入浅出的方式介绍如何利用Node.js及其强大的Express框架来搭建一个高效的后端服务。我们将从零开始,逐步深入,不仅涉及基础的代码编写,更会探讨如何优化性能和处理高并发场景。无论你是后端新手还是希望提高现有技能的开发者,这篇文章都将为你提供宝贵的知识和启示。
|
1月前
|
JavaScript 前端开发
如何在 JavaScript 中实现块级作用域?
【10月更文挑战第29天】通过使用 `let`、`const` 关键字、立即执行函数表达式以及模块模式等方法,可以在JavaScript中有效地实现块级作用域,更好地控制变量的生命周期和访问权限,提高代码的可维护性和可读性。