9
作者:valentinogagliardi
译者:前端小智
来源:github

阿里云最近在做活动,低至2折,有兴趣可以看看:
https://promotion.aliyun.com/...


为了保证的可读性,本文采用意译而非直译。

第1章:JS 简介

什么是JavaScript

JS 是一种用于 web 的脚本语言。JS 诞生于 1995 年,由 Brendan Eich 一手创建,用于向web页面添加交互性。那时的互联网还处于起步阶段,我们今天看到的大多数花哨的网页在那时候还只是一个梦。

在项目经理的催促下,Brendan 只有 10 天的时间来创建一种可以在浏览器中运行的动态、灵活的语言。他写出了 JavaScript,一种有点奇怪的编程语言,它参考了 Java、C 和 Scheme。JS 一直名声不好,因为它从一开始就有很多怪异的地方。但尽管如此,它还是在名人堂占据了一席之地,并一直挺到了今天。

现在,JS 被用来创建整个应用程序,称为SPA(单页应用程序)。随着使用量的增加,JS 生态系统也经历了寒武纪大爆发。咱们今天用于开发 JS 的大多数 Web 工具和库,很多用 JS 写的。JS 也被用除前端方面的领域。使用 Node.js 咱们可以创建服务器端和物联网应用程序,工业设备,以及更多。但最重要的是,单页应用程序是 JS 最突出的用法之一。

在单页面应用中,JS 负责所有的事情,使 UI 流畅,无需任何页面刷新。从用户的角度来看,这是对传统 web 应用程序的巨大改进。但是,能力越大,责任越大: JS 对于大多数移动设备来说是一个沉重的负担,在设计和构建时应该格外小心。、

为什么要学 JavaScript

今天学习 JS 并不意味着对变量和函数的肤浅理解:还有很多。JS 开发人员知道闭包、thisnew、原型系统和更新的特性。JS 一年比一年流行,功能也逐渐完善。现在几乎每个前端开发人员的工作都需要 JS 知识。招聘经理寻找的不是会使用 JQ (说到jQuery,它似乎正在慢慢消亡) 的。

大多数情况下,你也需要解及学习 TypeScript, 强调类型的 JS。前端开发人员应该要理解 JS 的特性,并能够编写惯用的、结构良好的 JS 代码。JS 正在迅速传播,即使你不喜欢这种语言,在这一点上忽视它也可能对你的职业生涯不利。

第2章:JS 基础

JS 目前有 7 种基本类型,如下:

  • String
  • Number
  • Boolean
  • Null
  • Undefined
  • Object
  • Symbol(ES6)

除了 Object 是复杂数据类型外,其它的 6 种是 JS 的基本数据类型。每个 JS 类型都有一个对应的表示,可以在咱们的代码中使用,比如字符串:

var string = "Hello John";

数字:

var age = 33;

说到这里,JS 也有算术运算:

运算符 运算名
+ 加法
++ 自增
* 乘法
** 指数
-
-- 自减
/
% 取除

在 JS 中,可以使用 var 关键字将值存储在变量中,这是声明变量的最兼容方法:

var greet = "Hello";
var year = 89;
var not = false;

这里说的兼容,是因为在 ES6 中我们还有两个选择: letconst。旧的浏览器可能不支持这些新的关键字,除非使用“转置器”,否则可能会遇到错误。在新的浏览器中,建议都 letconst 。主要有两个好处:

  • letconst 都有自己的块作用域
  • const 不能重新分配,也不能重新声明

块作用域是指用 letconst 声明的变量与在封闭或外部中声明的相同变量名不重叠。例如:

let name = "前端小智";

{
  let name = "王大冶";
  console.log(name); // "王大冶"
}

console.log(name); // "前端小智"

这里的 name 似乎是重复的,但实际上是两个不同的变量在自己的作用域里。const 具有相同的行为:

const name = "前端小智";

{
  const name = "王大冶";
  console.log(name); // "王大冶"
}

console.log(name); // "前端小智"

var 的行为就与 letconst 不一样了。

var name = "前端小智";

{
  var name = "王大冶";
  console.log(name); // "王大冶"
}

console.log(name); // "王大冶"

正如前端所说,const 不能被重新分配,也不能在同一个作用域中重新声明。如果你尝试重新声明一个 const,会得到 "SyntaxError: Identifier has already been declared"。如果将某个值重新赋值给同一个 const,会得到 "TypeError: Assignment to constant variable" 错误。

const name = "前端小智";
const name = "王大冶";

// SyntaxError: Identifier 'name' has already been declared

下面代码也会抛出错误:

const name = "前端小智";
name = "王大冶";

// TypeError: Assignment to constant variable.

但是,请注意,这里所说的 “cons 不能重新分配,也不能重新声明” 时,并不意味着const 是不可变的。

这是初学者都会遇到的问题。事实上,任何稍微复杂一点的 JS 数据结构,如数组或对象,即使在分配给 const 时,它们的值或者属性值是可变的,不可变是指这些复杂对象的内存地址。

const person = {
  name: "前端小智",
  age: 21
};

person.name = "王大冶";

console.log(person);

// {name: "王大冶", age: 21}
    

const 对象中的不可变是指什么? 下面是数组:

const list = [1, 1, 3, 2, 5];

list.shift();

console.log(list); // [ 1, 3, 2, 5 ]

同样,不是不可变。 有人说 “const 是不可变” 时,请给他看这些例子。 现在回到基础。 除了独立变量之外,还可以使用字面量的方式声明数组:

var array = ["Hello", 89, false, true];

0 开始的索引可以访问数组元素:

var array = ["Hello", 89, false, true];
var first = array[0]; // "Hello"

几乎所有 JS 实体都附加了一些函数,称为方法。举两个例子,数组有很多处理自身的方法

var array = ["Hello", 89, false, true];

array.push(99);
array.shift();

console.log(array); // [ 89, false, true, 99 ];

对于字符串也是一样的:

var string = "John";
console.log(string.toUpperCase()); // JOHN

在第 5 章中,你会知道这些方法从何而来,但这里有一个提示:它们分别在 Array.prototype 和 String.prototype 上定义。除了方法之外,还有一些属性对于提取关于字符串长度的信息非常有用:

var string = "John";
console.log(string.length); // 4

或者数组的长度:

var array = ["Hello", 89, false, true];

array.push(99);
array.shift();

console.log(array.length); // 4

这些属性有些特殊,因为它们被称为 "getters"/"setters"。 你可以想象一个给定的字符串就像一个附加了一堆方法和属性的对象。当访问数组的长度时,你只需调用相应的 gettersetter 函数用于设置操作:

var array = {
  value: ["Hello", 89, false, true],
  push: function(element) {
    //
  },
  shift: function() {
    //
  },
  get length() {
    // gets the length
  },
  set length(newLen) {
    // sets the length
  }
};

// Getter call
var len = array.length

// Setter call
array.length = 50;

现在,咱们已经奠定了基础,让我们仔细看看对象,它是最重要的 JS 类型之一。

站在一个对象的肩膀上

Object 是 JS 中最重要的类型,因此几乎所有其他实体都可以从中派生。 例如,函数和数组是专用对象。 JS 中的对象是键/值对的容器,如以下示例(字面量形式):

var obj = {
  name: "John",
  age: 33
};

还有另一种创建对象的方法,但它很少见,性能低,请避免使用这种形式:

var obj = new Object({
  name: "John",
  age: 33
});

正如你所看到的,对象是保存值的一种方便方法,稍后可以通过访问相应的属性来检索这些值:

var obj = {
  name: "前端小智",
  age: 26
};

console.log(obj.name); // "前端小智"

咱们还可以添加新属性、删除或更改它们

var obj = {
  name: "前端小智",
  age: 26
};

obj.address = "王大冶";
delete obj.name;
obj.age = 18;

对象的键也可以是字符串,在本例中,我们使用方括号符号访问属性:

var obj = {
  name: "前端小智",
  age: 26,
  "complex key": "stuff"
};

console.log(obj["complex key"]); // "stuff"

但是,表示法更常见,除非键是复杂的字符串,否则应该选择传统的属性访问:

var obj = {
  name: "前端小智",
  age: 26
};

console.log(obj.name); // "前端小智"

这是咱们所有需要知道的基本知识,但在 第5章,我们将看到 JS 对象是非常强大的,可以做更多。现在来看看 JS 函数。

5 种不同的 JS 函数

几乎每种编程语言都有函数,JS 也不例外。函数是可重用的代码段。考虑以下示例

function hello(message) {
  console.log(message);
}

hello("Hello");

function sum(a, b) {
  return a + b;
}

var sum = sum(2, 6);

第一个函数打印一个字符串,第二个函数向外部世界返回一个值。正如你所看到的,函数可以接受参数,列在函数“签名”中:

// a 和 b 是函数签名中的参数
function sum(a, b) {
  return a + b;
}

咱们可以在调用函数时传递值:

// a and b are parameters in the function's signature
function sum(a, b) {
  return a + b;
}

// 2 和 6 是该函数的参数
var sum = sum(2, 6);

function 关键字声明的 JS 函数是常规函数,与没有主体的肩头函数相反常规函数可以呈现多种形式:

  • 命名函数
  • 匿名函数
  • 对象方法
  • 对象方法简写(ES 6)
  • IIFE(立即执行函数)

命名函数是最传统的函数类型:

function sum(a, b) {
  return a + b;
}

另一方面,匿名函数没有名称,可以分配给一个变量供以后使用

var sum = function(a, b) {
  return a + b;
};

或者用作其他函数中的回调:

var button = document.createElement("button");

button.addEventListener("click", function(event) {
  // do stuff
});

函数也可以存在于对象中,这种称为该对象的方法

var widget = {
  showModal: function() {
    // do stuff
  }
};

widget.showModal();

常规函数在默认情况下也会得到一个 this 关键字,它可以根据调用函数的方式赋予不同的含义。在第六章中,我们将详细探讨这个主题。现在有一个简单的规则:在一个对象内部运行的函数有 this 指向包含对象的指针

var widget = {
  html: "<div></div>",
  showModal: function() {
    console.log(this.html);
  }
};

widget.showModal(); // "<div></div>"

在 ES6 中,你也可以使用对象方法简写:

var widget = {
  showModal() {
    // object method shortand
  }
};

widget.showModal();

最后,IIFE (立即执行的函数):

var IIFE = (function() {
  // what happens in an IIFE stays in the IIFE
})();

语法可能看起来有点奇怪,但是 IIFE 非常强大,在第4章会看到它们。除了常规函数外,还有箭头函数,在 ES6 中添加。箭头函数不使用 function 关键字,但它们具有相似的形式:

  • 命名箭头函数
  • 匿名箭头函数
  • 对象方法
  • IIFE 箭头函数

箭头函数很方便,但我建议不要过度使用它们。这是一个命名的箭头函数。如果没有参数,可以省略 return 语句并使用圆括号

const arrow = () => console.log("Silly me");

如果你需要在箭头函数中计算一些东西或者调用其他函数,可以用花括号包含一个主体

const arrow = () => {
  const a = callMe();
  const b = callYou();
  return a + b;
};

花括号也是定义对象的字面量形式,这并不意味着咱们可以做类似的事情:

const arrow = () => {
  a : "hello", 
  b: "world"
};

这是无效的语法。要从箭头函数返回对象,可以使用圆括号:

const arrow = () => ({
  a: "hello",
  b: "world"
});

console.log(arrow());
// { a: 'hello', b: 'world' }

或者使用 return 语句:

const arrow = () => {
  return {
    a: "hello",
    b: "world"
  };
};

与常规匿名函数一样,也有匿名箭头函数。这里有一个作为回调传递给另一个函数

const arr = [1, 2, 3];

const res = arr.map(element => element + 1);

console.log(res); // [ 2, 3, 4 ]

它以 element 为参数,并为每个数组元素返回 element +1。 如你所见,如果箭头函数只有一个参数,则无需在其周围加上括号:

const fun = singleParameter => singleParameter + 1;

但如果你需要更多的参数,括号是必需的:

const fun = (a, b) => a + b + 1;

箭头函数也可以作为对象方法出现,但是它们的行为与常规函数不同。在前一段介绍了 this 关键字,它是对运行函数的对象的引用。当作为对象方法调用时,常规函数将 this 指向宿主对象

var widget = {
  html: "<div></div>",
  showModal: function() {
    console.log(this.html);
  }
};

widget.showModal(); // "<div></div>"

而箭头函数中的 this 则指向完全不同的东西:

var widget = {
  html: "<div></div>",
  showModal: () => console.log(this.html)
};

widget.showModal(); // undefined

因此,箭头函数不太适合作为对象方法,但是有一些有趣的用例,在本小册中,咱们将了解为什么以及何时有效使用它们。 最后,来看一下 IIFE 箭头函数:

(() => {
  console.log("aa");
})();

令人困惑的语法不是吗? 接着咱们将进入下一章。

传递参数

ECMAScript 中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。有不少开发者在这一点上可能会感到困惑,因为访问变量有按值和按引用两种方式,而参数只能按值传递。

在向参数传递基本类型时,被传递的值会被复制给一个局部变量(即命名参数,或者用 ECMAScript 的概念来说,就是 arguments 对象中的一个元素)。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。请看下面的例子:

function addTen(){
  num += 10;
  return num
}

var count = 20;
var result = addTen(count);
alert(count); // 20 没有变化
alert(result); // 30

这里的函数 addTen () 有一个参数 num ,而参数实际上是函数的局部变量。在调用这个函数时,变量 count 作为参数被传递给函数,这个变量的值是 20。于是,数值 20 被复制给参数 num 以便在 addTen() 中使用。 在函数内部,参数 num 的值被加上了 10,但这一变化不会影响函数外部的 count 变量。参数的值也将变成 30,从而反映函数内部的修改。当然,使用数值等基本类型值来说明按值传递参数比较简单,但如果使用对象,那问题就不怎么好理解了。再举一个例子:

function setName (obj) {
  obj.name = '前端小智';
}

var person = new Object();
setName(person);
alert(person.name) // "前端小智"

以上代码创建一个对象,并将其保存在了变量 person 中。然后,这个变量被传递到 setName() 函数中之后就被复制给了 obj。在这个函数内部, obj 和 person引用的是同一个对象。于是,当在函数内部为 obj 添加 name 属性后,函数外部的 person 也将有所反映;因为person指向的对象在堆内存中只有一个,而且是全局对象。

有很多开发者错误的认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递。为了证明对象是按值传递的,我们再看一看下面这个经过修改的例子:

function setName(obj) {
  obj.name = '前端小智';
  obj = new Object();
  obj.name = '王大冶'
}
var person = new Object();
setName(person);
alert(person.name) // '前端小智'

这个例子与前一个例子的唯一区别,就是在 setName() 函数中添加了两行代码:一行代码为 obj 重新定义了一个对象,另一行代码为该对象定义了一个带有不同值的 name 属性。在把 person 传递给 setName() 后,其 name 属性被设置为 ‘前端小智’。然后,又将一个新对象赋给变量 obj,同时将其 name 属性设置为 '王大冶'

如果 person 是按引用传递的,那么 person 就会自动被修改为指向其 name 属性为 ‘王大冶'的新对象。但是原始的引用仍然保持不变。实际上,当在函数内部重写 obj 时,这个变量引用就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。

总结

JS 具有七个称为 “类型” 的基本构建块,其中 6 个也称为基本数据类型。 Object 本身就是一种类型,也是该语言最重要的实体。 对象是用于一对键/值的容器,并且可以包含几乎所有其他 JS 的类型,包括函数。

与大多数其他编程语言一样,JS 有字符串、数字、函数、布尔值和一些称为 NullUndefined 的特殊类型。JS 中有两种函数:箭头函数和常规函数。它们都有各自的用法,根据场景使用它们。

思考

  • arguments 和 参数 之间有什么区别?
  • JS 中有多少个基本类型
  • 什么是常规的箭头函数
  • 函数作为对象方法运行时可以访问哪些特殊关键字?
  • 在 JS 中声明变量有多少种方法

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具Fundebug

原文:

https://github.com/valentinog...

https://github.com/valentinog...

交流

阿里云最近在做活动,低至2折,有兴趣可以看看:https://promotion.aliyun.com/...

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq449245884/xiaozhi

因为篇幅的限制,今天的分享只到这里。如果大家想了解更多的内容的话,可以去扫一扫每篇文章最下面的二维码,然后关注咱们的微信公众号,了解更多的资讯和有价值的内容。

clipboard.png

每次整理文章,一般都到2点才睡觉,一周4次左右,挺苦的,还望支持,给点鼓励


王大冶
68.1k 声望105k 粉丝