【CSDN 编者按】大家都说 JavaScript 很怪异,可明明它又没做错什么,难道带来高效和方便也是一种错吗!让我们一起跟着作者来走进 JavaScript 的世界,来感受它的酷!
我是一个单线程、非阻塞、异步并发语言。
我有一个调用栈、一个事件循环、一个回调队列和其他一些 API。
—— JavaScript
你可以让应用程序获取一个 API。你的按钮使用全局状态。但是每当控制台中出现红色警告,你都会向 Stack Overflow 哭诉。
使用框架构建产品很方便。在实践中学习很酷。但是,由于缺乏深入了解,你肯定会碰壁。
所以我报名了网上的课程。
问题:你会使用 JavaScript 编写代码,但计算机不理解。
解决方案我们来简化:
语法解析器变成……
高级代码 -> 数据结构(树或图)
然后由编译器翻译……
数据结构 -> 低级语言(机器码)
请不要与转译相混淆,转译是一种高级编程语言到另一种的语法转换,比如 TypeScript -> JS。
在理解 JavaScript 的运行之前,我们首先需要理解执行上下文是什么。
执行上下文是执行一段代码的环境。输入 this,你就可以看到上下文的信息。上下文的内容取决于你是否在函数内部,但通常包含:
本地代码(变量、对象、函数)
全局对象(通过浏览器的 window 访问)
词法环境(对其外部环境的引用)
执行上下文分两个阶段执行:
创建阶段:为变量和函数建立内存空间。
代码执行阶段:逐行运行代码(详细说明见下)。
在创建阶段,最初所有的变量都会被设置为 undefined,而函数则完全位于内存中。但是请注意, “undefined ” 不同于 “ReferenceError: my_var is not defined”,后者是指变量尚未分配内存空间。
在运行程序时,会创建全局执行上下文;接着,每当调用函数时,其执行上下文就会被放到执行栈的顶部。
请记住,JavaScript 是单线程的,这意味着它只有一个调用栈。此外,JavaScript 是同步的,这意味着每个命令都是按顺序执行的。
每个函数都会创建自己的执行上下文,而该上下文将贯穿创建和执行阶段。当栈顶部的函数运行完成后,就会被弹出。
我们来看看下面这段代码:
function b() {
console.log(myVar);
}
function a() {
var myVar = 2;
b();
}
var myVar = 1;
a();
正确答案是1。为什么不是 undefined?
因为如果当前执行上下文中没有定义某个变量,则它会去外部环境寻找(在上面的代码中是全局执行上下文)。作用域链就是这个外部环境的引用链。
JavaScript是动态类型,也就是说没有关键字定义变量指向的数据类型。JavaScript 与 TypeScript 的主要区别就在于此,TypeScript 是静态类型。
基本类型是一种表示单个值的数据类型(不是键值对或对象)。
JavaScript 中有六种基本类型:
undefined:"不存在"。
null:也是“不存在”,但它是一个确定的值。
boolean :true 或 false。
number:浮点数,也就是说数字永远是小数(JavaScript 中唯一的数字类型)。
string:文本。
symbol:仅限于 ES6 。
运算符是语法上不同的函数。通常运算符接受两个参数,并返回一个结果。
与普通的函数不同,运算符使用中缀表示法,即 3+4,而不是前缀表示法 +(3,4) 或后缀表示法 (3, 4)+。
运算符优先级:优先调用哪个函数。
运算符结合律:函数被调用的顺序(从左到右,或从右到左)。
强制转换指的是将值从一种类型转换为另一种类型,例如:
var a = 1 + '2';
有时候,强制转换的行为会非常怪异:
console.log(3 < 2 < 1); // true
console.log(Number(undefined)); // Nan
console.log(Number(null)); // 0
相等(==)会执行强制转换,而严格相等(===)不会:
console.log(Number('3' == 3)); // truthy
console.log(Number('3' === 3)); // falsy
换句话说,我们应该尽可能使用严格相等,否则代码就会出现一些预料之外的行为。
最后,我们可以使用强制转换来检查某个变量是否已定义(但要小心零!)
if (a || a === 0) {
console.log("There's a there there.");
}
JSON 的键需要使用引号,而对象不需要。因此,所有 JSON 都可以用作对象,反之则不然。
但由于二者非常相似,JavaScript 为 JSON 提供了许多内置功能:
JSON.stringify():获取一个对象,并输出字符串形式的 JSON。
JSON.parse():接受一个字符串,并输出一个对象。
本质上,{} 与 new Object() 相同,但使用字面量创建的对象是全局的。而构造函数则允许我们创建对象的实例。
JavaScript 中的函数有着举足轻重的地位,也就是说函数可以当做任何其他对象或类型进行处理(可以被创建、传递等等),这就是 JavaScript 非常适合函数式编程的原因。
虽然,函数与对象的不同指出主要表现在以下两个方面:
匿名:可以不指定名称。
拥有可调用的代码。
当一个函数被附着到一个对象时,函数范围内的 this 会指向该对象,而匿名函数中的 this 指向的是它的创建者。
最后,所有基本类型都是值传递,所有对象(包括函数)都是引用传递。
基本类型的值变量 a 位于内存中的某个位置(例如 0x001)。如果我们将新的 var b 传递给该 var a,它会在内存的其他位置创建一个基本类型的值的副本(例如 0x002)。这是值调用(即将值复制到内存中的两个单独的位置)。这意味着我们之后修改 a 时 b 不会受影响 。
然而,当我们复制一个对象变量时,新变量就会指向内存中的同一个位置(也称为引用):
a = new Object(); // 0x001
b = a; // 0x001
可怜的 JavaScript,他们都说你很怪异。
这不公平,你不应该害怕。
有人理解你的怪异之处,而他们会为你喝彩。
其实你很高效,缺陷也不是太多。
原文链接:https://broman.blog/relearning-js
本文为 CSDN 翻译,转载请注明来源出处。