前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >3《JavaScript高级程序设计》__ 语言基础(上)

3《JavaScript高级程序设计》__ 语言基础(上)

作者头像
HoMeTown
发布2022-10-26 08:58:35
6330
发布2022-10-26 08:58:35
举报
文章被收录于专栏:秃头开发头秃了

前言

大家好,我是HoMeTown,web领域有一本神书大家应该都有看过,这本书我看过两遍,但是每次看都是粗粗的略过一些重要的知识点,甚至一些面试过程中的问题,在这本书里都能找到答案。

工作这么多年,到现在为止对这本书都没有一个系统的知识点记录,这次想从头读一遍这一本JavaScript高级程序设计【第4版】,并把重要的知识点记录下来,同时加上自己的见解,这也是我第一次在掘金上记录分享读书笔记,共勉之!

关注专栏,一起学习吧~

区分大小写

在JavaScript中,一切都区分大小写,testTest是两个不同的变量

标识符

标识符可以由一个或多个字符组成,字符包含:

  • 第一个字符必须是一个字母,下划线_ 或者美元符号$
  • 其他的字符可以使字母、下划线、美元符号、数字

严格模式

strict modeECMAScript3的一些不规范写法会被处理,使用方法是 在脚本开头写上"use strict"

也可以指定一个函数下执行严格模式:

代码语言:javascript
复制
function func() {
  "use strict"}

严格模式下,这些操作是不被允许的:

代码语言:javascript
复制
"use strict";// 不能给未声明的变量赋值,则会导致抛出 ReferenceErrorfunction func1() {
  message = "hometown";  console.log(message); //  Uncaught ReferenceError: message is not defined}// 不能定义名为 eval 和 arguments 的变量,否则会导致语法错误function func2() {  var eval = "1"; // Uncaught SyntaxError: Unexpected eval or arguments in strict mode}func2();// 不允许使用八进制的数字字面量const num = 070...持续更新...

变量

var 声明变量

变量声明
代码语言:javascript
复制
var message = 'HoMeTown'// ---- 也可以这样 ----message = "HoMeTown" // 合法但不推荐
作用域

在一个函数内声明变量,改变了会成为函数内部的局部变量,调用函数时,声明变量并赋值,随着函数执行完毕,该变量会被立即销毁,无法再次访问:

代码语言:javascript
复制
function func() {
  var message = "hometown";
  console.log(message); // hometown}func();
console.log(message); //var.html:16 Uncaught ReferenceError: message is not defined

如果去掉前面的关键字var,那么变量message就会变成全局变量,外部也可以访问:

代码语言:javascript
复制
function func() {
  message = "hometown";
  console.log(message); // hometown}func();
console.log(message); // hometown

两者的区别其实就在于,后者会直接在全局对象window上创建一个属性message,但是前者不会。

前者:

代码语言:javascript
复制
console.log(window.message) // undefined

后者:

代码语言:javascript
复制
console.log(window.message) // hometown

后者的写法也就等价于window.message = "hometown"

虽然可以通过省略var 操作符定义全局变量,但不推荐这么做。在局部作用域中定

义的全局变量很难维护,也会造成困惑。这是因为不能一下子断定省略 var 是不是有意而

为之。在严格模式下,如果像这样给未声明的变量赋值,则会导致抛出 ReferenceError。

声明提升

一般情况下,直接使用一个未声明的变了,会抛出错误,像这样:

代码语言:javascript
复制
function func() {
  console.log(message); // var.html:30 Uncaught ReferenceError: message is not defined}func();

使用var声明变量时,即使在变量使用之后才声明,也不会报错,像下面这段代码:

代码语言:javascript
复制
function func() {
  console.log(message); // undefined
  var message = "hometown";
}func();

这就是var声明变量提升(hoist)。也就是先把所有var声明全部都拉到顶部,然后再在当前位置赋值,例如上面的这段代码,可以理解为:

代码语言:javascript
复制
function func() {
  var message;
  console.log(message)
  message = "hometown"}func()

这个东西前几年面试还问的挺多的。

let 声明变量

变量声明

let声明变量与var一样

代码语言:javascript
复制
let message = "hometown";
块级作用域

letvar有一个显著的区别就是,let声明变量具有块级作用域,而var函数作用域,举一个例子:

代码语言:javascript
复制
if (true) {
  var message = "hometown";
  console.log(message); // hometown}console.log(message); // hometownif (true) {
  let age = 18;
  console.log(age); // 18}console.log(age); // Uncaught ReferenceError: age is not defined

上面这个例子中,可以看到age在if块之外使用时,抛出了异常,age 变量之所以不能在 if 块外部被引用,是因为它的作用域仅限于该块内部。块作用域函数作用域子集,因此适用于 var 的作用域限制同样也适用于 let。

不能重复声明

同一个块级作用域中不能使用let重复声明变量,只要这个变量之前声明过,就不行:

代码语言:javascript
复制
function func() {
  var message = "hometown";
  var message = "hometown";
  console.log(message); // hometown}
func();function func2() {
  // var age = 18; // Cannot redeclare block-scoped variable 'age'.
  let age = 18; // Cannot redeclare block-scoped variable 'age'.
  let age = 22; // Uncaught SyntaxError: Identifier 'age' has already been declared
  console.log(age);}
func2();

如果不在同一块级作用域,是可以声明的:

代码语言:javascript
复制
function func() {
  let message = "hometown";
  if (true) {
    let message = "inside";
    console.log(message); // hometown
  }
  console.log(message); // inside}
func();

其实这样也不行:

代码语言:javascript
复制
let age = 18; // Cannot redeclare block-scoped variable 'age'var age = 22; // Uncaught SyntaxError: Identifier 'age' has already been declared

因为这样varhoist,其实就等价于:

代码语言:javascript
复制
var age;let age = 18;age = 22;

所以在使用let声明变量时,一定要确保当前块级作用域没有声明过该变量

暂时性死区

let声明的变量不会像var那样变量提升!也就意味着,只要在使用变量的时候,这个变量没有被声明过,那就会报错!即使你在后面声明了该变量:

代码语言:javascript
复制
console.log(message); // undefinedvar message = "hometown";console.log(age); // let.html:60 Uncaught ReferenceError: Cannot access 'age' before initializationlet age = 18;

变量提升反过来对应的就是暂时性死区,我觉得变量提升简直就是个设计缺陷,书中也讲到,尽量避免使用var,多使用let & const,鬼知道使用var时不时的会带来一些什么问题。

不会添加到window

使用var在全局声明变量时,会直接在window上添加属性,使用let声明则不会:

代码语言:javascript
复制
var message = "hometown";console.log(window.message); // hometownlet age = 18;console.log(window.age); // undefined
for循环中的let

let出现之前,for循环中的迭代变量会渗透到循环外部:

代码语言:javascript
复制
for (var i = 0; i < 5; i++) {}
console.log(i); // 5

改为let以后,这个问题就好了:

代码语言:javascript
复制
for(let i = 0; i < 5; i++) {}console.log(i); // Uncaught ReferenceError: i is not defined

还有一个面试常见问题:

代码语言:javascript
复制
for (var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i);
  }, 0);
}
// 结果: 5 5 5 5 5

这块其实有点涉及时间循环与宏任务的知识,for循环作为一个同步任务开始执行,然后循环产生一个宏任务setTimeout,循环完毕之后,此时用var声明的全局的i已经自增到了5,我们可以打个debugger,然后再Sources中看到,此时i在这个地方:

值为下图:

当我们修改为let声明后,我们可以看到:

每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。

const声明变量

与let一样,不同点是,const声明的变量不能被修改。

变量声明的最佳实践

  • 不使用var
  • const优先、let次之

数据类型

Js中有6中数据类型简单数据类型:

  • Number
  • String
  • Boolean
  • Null
  • Undefined
  • Symbol

后面好像还加了一个BigInt

typeof

代码语言:javascript
复制
typeof null // object

因为null在js中被认为是一个空对象的引用。

undefined

undefined只有一个特殊值undefined,当声明了一个变量,未对其进行初始化时,默认为undefined

但是不能直接使用未声明的变量:

代码语言:javascript
复制
let message;console.log(message); // undefinedconsole.log(age); // 报错

有趣的是,typeof undefined === undefined typeof 未定义的值 === undefined:

代码语言:javascript
复制
let message;console.log(typeof message, typeof age) // undefined undefined

所以,尽量在定义变量的时候进行初始化,这样进行typeof操作时,只要返回undefined就知道,这个变量一定是没有什么过,而不是声明了没有赋值。

null

null也只有一个特殊值null,null表示一个空对象指针,这也是为什么typeof null === 'object'的原因。

书中建议使用 null 初始化一个对象,比如:

代码语言:javascript
复制
let obj = null

但是这个规则好像到typescript中不太奏效。因为在TypeScript中完全校验值的类型,null 与 {} 是两种不同的类型。

undefined是由null派生的,所以undefined == null返回true, 但是undefined === null返回false

书中推荐,永远不要给一个值赋值undefined,这样我们一旦遇到undefined就立马可以知道这个变量没有声明。可以给一个值赋值null,证明他是一个假值,一个空对象指针。

Boolean

Boolean值的可选值:true、false。

在JavaScript中,true不全等与1,false不全等于1。

Number

JavaScript采用的是IEEE 754格式表示整数和浮点数(双精度)。

整数

最基本的即十进制表示:

代码语言:javascript
复制
let num = 100

八进制字面量的第一个数字必须是0

代码语言:javascript
复制
let num = 070 // 八进制的56

如果后面的值超出了数值0 ~ 7,默认省去0,按照十进制处理:

代码语言:javascript
复制
let num = 079 // 按照十进制的79处理

十六进制的字面量必须以0x开头:

代码语言:javascript
复制
let num = 0xA // 十六进制的10
浮点数

JavaScript会自动判断你的浮点数是否有意义,比如1.0,这样的数会自动保存成1,因为存储一个浮点数的开销比存储一个整数大得多。

可以用浮点数 + 科学计数法来表示一个大数:

代码语言:javascript
复制
const num = 3.123e7 // 31230000

字母e后面的数字表示再加上一个要乘的 10 的多少次幂,反之一样,比如31230000e-7

0.1 + 0.2 != 0.3,而是0.300 000 000 000 000 04,双精度和单精度计算出来的结果还不一样,这是是因为使用了 IEEE 754 数值,这种错误并非 ECMAScript所独有。其他使用相同格式的语言也有这个问题。

部分浮点数在十进制下是有限小数,但是在二进制下尾数是无限数,比如0.1、0.2 尾数0011无限循环。

由于计算精度有限,计算机底层根据IEEE 754舍入规则进行处理后存储了一个近似值(近似值的长度与精度有关),近似值0.3值0.3 在二进制上表现不同,是不能用来比较的,因为近似值和任何数比较都可能返回false。

值的范围
代码语言:javascript
复制
Number.MIN_VALUE // 最小值 5e-324Number.MAX_VALUE // 最大值 1.7976931348623157e+308

如果超出则返回Infinity or -Infinity

也可以通过Number.NEGATIVE_INFINITY === -Infinity & Number.POSITIVE_INFINITY === Infinity 获取这两个值。

判断一个值是不是无穷值,可以通过isFinite

代码语言:javascript
复制
isFinite(Number.POSITIVE_INFINITY) // falseisFinite(1) // true
NaN

NaN(Not a Number),表示本来要返回数值的操作失败了:

代码语言:javascript
复制
0/0 // NaN5/0 // Infinity5/-0 // -Infinity

NaN和任何值比较都返回false哪怕是自身:

代码语言:javascript
复制
NaN === NaN //false

判断一个值是否是 不是一个数字,可以通过isNaN

代码语言:javascript
复制
isNaN(NaN) // trueisNaN(10) // falseisNaN('10') // falseisNaN('aaa') // trueisNaN(true) // false

NaN实现会调用对象的valueOf方法,然后确定返回的值是否可以转换成数值,如果不能,再调用toString方法,并测试其返回值。

数值转换

3个内置函数,将非数值转换为数值:

代码语言:javascript
复制
Number()
 + Number(true) ---> 1
 + Number(false) ---> 0
 + Number('') ---> 0
 + Number('1') ---> 1
 + Number('asd') ---> NaN
 + Number('0123') ---> 123
 + Number({}) ---> NaN
 + Number(new Number(1)) ---> 1
 + Number(undefined) ---> NaN
 + Number(null) ---> 0
 + Number(070) ---> 56// Number传入对象时,会调用对象的valueOf方法parseInt()
 + parseInt(true) ---> NaN
 + parseInt(false) ---> NaN
 + parseInt('') ---> NaN
 + parseInt('1') ---> 1
 + parseInt('0123') ---> 123
 + parseInt('asd') ---> NaN
 + parseInt(undefined) ---> NaN
 + parseInt(null) ---> NaN
 + parseInt(new Number(1)) ---> 1
 + parseInt(1.3) ---> 1
 + parseInt(1.9) ---> 1
 + parseInt('1.3') ---> 1
 + parseInt('11aa') ---> 11
 + patseInt('aa11') ---> NaN// 不同的数值格式很容易混淆,因此parseInt也接受第二个参数,用于指定「进制数」
 + parseInt('0xAF', 16) ---> 175 // 按照16进制进行解析
 + parseInt('070', 8) ---> 56 // 按照8进制进行解析
 + parseInt('10', 10) ---> 10 // 按照10进制进行解析
 + parseInt('010', 2) ---> 2 // 按照2进制进行解析
 + parseInt('010') ---> 10 // 按照10进制进行解析parseFloat()
 + parseFloat('1.2.3.4') ---> 1.2
 + parseFloat('0xAF') ---> 0
 + parseFloat("0908.5") ---> 908.5
 + parseFloat('3.998e7') ---> 39980000// parseFloat与parseInt基本类似,float支持小数,解析到第一个小数点,后面再有小数点就不解析了,parseFloat只支持10进制

String

字面量

字符串的3种字面量:

代码语言:javascript
复制
const age = '12'const name = "HoMeTown"const gender = `male`
字符
  • \n 换行
  • \t 制表
  • \b 退格
  • \r 回车
特点

字符串一旦声明,就不能修改了。

代码语言:javascript
复制
let name = 'HoMeTown'name[2] = 'O'console.log(name) // HoMeTown
转换为字符串
toString
代码语言:javascript
复制
true.toString() // truelet age = 1age.toString() // 1 ()let opt = {}
opt.toString() // [object Object]null.toString() // null没有toStringString(null) // 'null'undefined.toString() // undefined没有toStringString(undefined) // 'undefined'Object.prototype.toString.call(true) // [object Boolean]

toString一般情况下不接收参数,用在数值上的时候,可以传入一个「进制数」,得到该数值的x进制:

代码语言:javascript
复制
let num = 10; 
console.log(num.toString()); // "10"console.log(num.toString(2)); // "1010"console.log(num.toString(8)); // "12"console.log(num.toString(10)); // "10"console.log(num.toString(16)); // "a"
模板字符串

将表达式转换为字符串时会调用

代码语言:javascript
复制
let foo = {
  name: 1,
  toString() {
    console.log("完了,我被调用了");
  },
};let num = new Number(1);
num.toString = function () {
  console.log("数字对象");
  return "改一下";
};const fooStr = `${foo}`;const numStr = `${num}`;console.log(numStr); // 改一下

Symbol

Symbol 是原始值,并且是唯一的、不可变的。

代码语言:javascript
复制
let sym = Symbol();console.log(typeof sym); // symbollet firstSym = Symbol("hello");let lastSym = Symbol("hello");console.log(firstSym); // Symbol(hello)console.log(lastSym); // Symbol(hello)console.log(firstSym === lastSym); // false//   let tryNewSym = new Symbol(); // Uncaught TypeError: Symbol is not a constructor// 使用Symbol.for 可以使用之前创建过的symbollet forSym1 = Symbol.for("hello");let forSym2 = Symbol.for("hello");console.log(forSym1); // Symbol(hello)console.log(forSym2); // Symbol(hello)console.log(forSym1 === forSym2); // true// 使用Symbol.keyFor 可以查询全局注册表console.log(Symbol.keyFor(forSym1)) // helloconsole.log(Symbol.keyFor(forSym2)); // hello
Symbol属性

定义对象属性:

代码语言:javascript
复制
const s1 = Symbol("msg");const s2 = Symbol("gender");const s3 = Symbol("name");const s4 = Symbol("address");let opt = {  [s1]: "hello",
};console.log(opt); // {Symbol(msg): 'hello'}console.log(opt[s1]); // helloObject.defineProperty(opt, s2, { value: "male" });console.log(opt); // {Symbol(msg): 'hello', Symbol(gender): 'male'}Symbol(msg): "hello"Symbol(gender): "male"[[Prototype]]: Objectconsole.log(opt[s2]); // maleObject.defineProperties(opt, {  [s3]: {
    value: "HoMeTown",
  },  [s4]: {
    value: "北京",
  },
});console.log(opt); // ...Symbol(name): HoMeTown, Symbol(address): 北京

获取对象上的属性:

代码语言:javascript
复制
console.log(Object.getOwnPropertyNames(opt)); // ['id']console.log(Object.getOwnPropertySymbols(opt)); // [Symbol(msg), Symbol(gender), Symbol(name), Symbol(address)]console.log(Object.getOwnPropertyDescriptors(opt)); // {id: {…}, Symbol(msg): {…}, Symbol(gender): {…}, Symbol(name): {…}, Symbol(address): {…}}
console.log(Reflect.ownKeys(opt)); // ['id', Symbol(msg), Symbol(gender), Symbol(name), Symbol(address)]
内置 Symbol符号
Symbol.hasInstance

instanceof 的原理就是Symbol.hasInstance,用来检测一个对象实例的原型上是否有原型:

代码语言:javascript
复制
class Foo {}const foo = new Foo();console.log(foo instanceof Foo); // true

在ES6 中,instanceof 操作符会使用 Symbol.hasInstance 函数来确定关系。以 Symbol.

hasInstance 为键的函数会执行同样的操作,只是操作数对调了一下:

代码语言:javascript
复制
console.log(Foo[Symbol.hasInstance](foo)); // true

这个属性定义在 Function 的原型上。因此默认在所有函数和类上都可以调用。由于 instanceof

操作符会在原型链上寻找这个属性定义,就跟在原型链上寻找其他属性一样,因此可以在继承的类上通过静态方法重新定义这个函数:

代码语言:javascript
复制
class Foo {
  static [Symbol.hasInstance]() {
    return false
  }
}console.log(foo instanceof Foo); // false

Object

对象其实就是一堆数据和功能的集合。

每个 Object 实例都有如下属性和方法。

  • constructor:用于创建当前对象的函数。在前面的例子中,这个属性的值就是 Object()函数。
  • hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如 o.hasOwnProperty("name"))或符号。
  • isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型。
  • propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用for-in 语句枚举。与 hasOwnProperty()一样,属性名必须是字符串。
  • toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
  • toString():返回对象的字符串表示。
  • valueOf():返回对象对应的字符串、数值或布尔值表示。通常与 toString()的返回值相同。

总结

Js的数据类型:StringNumberNullUndefinedBolleanSymbolObject

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-10-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 秃头开发头秃了 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 区分大小写
  • 标识符
  • 严格模式
  • 变量
    • var 声明变量
      • 变量声明
      • 作用域
      • 声明提升
    • let 声明变量
      • 变量声明
      • 块级作用域
      • 不能重复声明
      • 暂时性死区
      • 不会添加到window
      • for循环中的let
    • const声明变量
      • 变量声明的最佳实践
      • 数据类型
        • typeof
          • undefined
            • null
              • Boolean
                • Number
                  • 整数
                  • 浮点数
                  • 值的范围
                  • NaN
                  • 数值转换
                • String
                  • 字面量
                  • 字符
                  • 特点
                  • 转换为字符串
                • Symbol
                  • Symbol属性
                  • 内置 Symbol符号
                • Object
                • 总结
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档