JavaScript高级程序设计(第4版)读书笔记:十二

JavaScript程序设计

4.4 单例内置对象

ECMA-262 对内置对象的定义是“任何由 ECMAScript 实现提供、与宿主环境无关,并在 ECMAScript程序开始执行时就存在的对象”。这就意味着,开发者不用显式地实例化内置对象,因为它们已经实例化好了。前面我们已经接触了大部分内置对象,包括 ObjectArrayString。本节介绍 ECMA-262定义的另外两个单例内置对象:GlobalMath

4.4.1 Global

Global 对象是 ECMAScript 中最特别的对象,因为代码不会显式地访问它。ECMA-262 规定 Global对象为一种兜底对象,它所针对的是不属于任何对象的属性和方法。事实上,不存在全局变量或全局函数这种东西。在全局作用域中定义的变量和函数都会变成 Global 对象的属性 。本书前面介绍的函数,包括 isNaN()isFinite()parseInt()parseFloat(),实际上都是 Global 对象的方法。除了这些,Global 对象上还有另外一些方法。

  1. URL 编码方法

    encodeURI()encodeURIComponent()方法用于编码统一资源标识符(URI),以便传给浏览器。有效的 URI 不能包含某些字符,比如空格。使用 URI 编码方法来编码 URI 可以让浏览器能够理解它们,同时又以特殊的 UTF-8 编码替换掉所有无效字符。

    ecnodeURI()方法用于对整个 URI 进行编码,比如"www.wrox.com/illegal value.js"。而encodeURIComponent()方法用于编码 URI 中单独的组件,比如前面 URL 中的"illegal value.js"。

    这两个方法的主要区别是,encodeURI()不会编码属于 URL 组件的特殊字符,比如冒号、斜杠、问号、井号,而 encodeURIComponent()会编码它发现的所有非标准字符。来看下面的例子:

    let uri = "http://www.wrox.com/illegal value.js#start"; 
    // "http://www.wrox.com/illegal%20value.js#start" 
    console.log(encodeURI(uri)); 
    // "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start" 
    console.log(encodeURIComponent(uri));
    

    这里使用encodeURI()编码后,除空格被替换为%20 之外,没有任何变化。而encodeURIComponent()方法将所有非字母字符都替换成了相应的编码形式。这就是使用 encodeURI()编码整个URI,但只使用encodeURIComponent()编码那些会追加到已有 URI 后面的字符串的原因。

    Note: 一般来说,使用 encodeURIComponent()应该比使用 encodeURI()的频率更高,这是因为编码查询字符串参数比编码基准 URI 的次数更多。

    encodeURI()encodeURIComponent()相对的是 decodeURI()decodeURIComponent()decodeURI()只对使用 encodeURI()编码过的字符解码。例如,%20 会被替换为空格,但%23 不会被替换为井号(#),因为井号不是由 encodeURI()替换的。类似地,decodeURIComponent()解码所有被 encodeURIComponent()编码的字符,基本上就是解码所有特殊值。来看下面的例子:

    let uri = "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start"; 
    // http%3A%2F%2Fwww.wrox.com%2Fillegal value.js%23start 
    console.log(decodeURI(uri)); 
    // http:// www.wrox.com/illegal value.js#start 
    console.log(decodeURIComponent(uri));
    

    这里,uri 变量中包含一个使用 encodeURIComponent()编码过的字符串。首先输出的是使用decodeURI()解码的结果,可以看到只用空格替换了%20。然后是使用 decodeURIComponent()解码的结果,其中替换了所有特殊字符,并输出了没有包含任何转义的字符串。(这个字符串不是有效的 URL。)

    Note:URI方法 encodeURI()encodeURIComponent()decodeURI()decodeURIComponent()取代了 escape()unescape()方法,后者在 ECMA-262 第 3 版中就已经废弃了。URI 方法始终是首选方法,因为它们对所有 Unicode 字符进行编码,而原来的方法只能正确编码 ASCII 字符。不要在生产环境中使用 escape()unescape()

  2. **eval()**方法

    这个方法可能是整个 ECMAScript 语言中最强大的了,它就是 eval()。这个方法就是一个完整的 ECMAScript 解释器,它接收一个参数,即一个要执行的 ECMAScript(JavaScript)字符串。来看一个例子:

    eval("console.log('hi')");
    

    上面这行代码的功能与下一行等价:

    console.log("hi");
    

    当解释器发现 eval()调用时,会将参数解释为实际的 ECMAScript 语句,然后将其插入到该位置。通过 eval()执行的代码属于该调用所在上下文,被执行的代码与该上下文拥有相同的作用域链。这意味着定义在包含上下文中的变量可以在eval()调用内部被引用,比如下面这个例子:

    let msg = "hello world"; 
    eval("console.log(msg)"); // "hello world"
    

    这里,变量 msg 是在 eval()调用的外部上下文中定义的,而 console.log()显示了文本"hello world"。这是因为第二行代码会被替换成一行真正的函数调用代码。类似地,可以在 eval()内部定义一个函数或变量,然后在外部代码中引用,如下所示:

    eval("function sayHi() { console.log('hi'); }"); // 函数声明在 JavaScript 中有特殊的提升(hoisting)行为,相当于在外部直接定义了 sayHi 函数
    sayHi();
    

    这里,函数 sayHi()是在 eval()内部定义的。因为该调用会被替换为真正的函数定义,所以才可能在下一行代码中调用 sayHi()。对于变量也是一样的:

    eval("let msg = 'hello world';"); // 此处使用let声明变量,具有块级作用域,因此在下一行无法访问到
    console.log(msg); // Reference Error: msg is not defined
    

    通过 eval()定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在 eval()执行的时候才会被创建。

    在严格模式下,在 eval()内部创建的变量和函数无法被外部访问。换句话说,最后两个例子会报错。同样,在严格模式下,赋值给 eval 也会导致错误

    "use strict"; 
    eval = "hi"; // 导致错误
    

    Note: 解释代码字符串的能力是非常强大的,但也非常危险。在使用 eval()的时候必须极为慎重,特别是在解释用户输入的内容时。因为这个方法会对 XSS 利用暴露出很大的攻击面。恶意用户可能插入会导致你网站或应用崩溃的代码

  3. Global 对象属性

    Global 对象有很多属性,其中一些前面已经提到过了。像 undefined、NaN 和 Infinity 等特殊值都是 Global 对象的属性。此外,所有原生引用类型构造函数,比如 Object 和 Function,也都是Global 对象的属性。下表列出了所有这些属性。

    属性说明
    undefined特殊值 undefined
    NaN特殊值 NaN
    Infinity特殊值 Infinity
    ObjectObject 的构造函数
    ArrayArray 的构造函数
    FunctionFunction 的构造函数
    BooleanBoolean 的构造函数
    StringString 的构造函数
    NumberNumber 的构造函数
    DateDate 的构造函数
    RegExpRegExp 的构造函数
    SymbolSymbol 的伪构造函数
    ErrorError 的构造函数
    EvalErrorEvalError 的构造函数
    RangeErrorRangeError 的构造函数
    ReferenceErrorReferenceError 的构造函数
    SyntaxErrorSyntaxError 的构造函数
    TypeErrorTypeError 的构造函数
    URIRrrorURIError 的构造函数
  4. window对象

    虽然 ECMA-262 没有规定直接访问 Global 对象的方式,但浏览器将 window 对象实现为 Global对象的代理因此,所有全局作用域中声明的变量和函数都变成了 window 的属性。来看下面的例子:

    var color = "red"; 
    function sayColor() { 
        console.log(window.color); 
    } 
    window.sayColor(); // "red"
    

    这里定义了一个名为color的全局变量和一个名为sayColor()的全局函数。在sayColor()内部,通过 window.color 访问了 color 变量,说明全局变量变成了 window 的属性。接着,又通过 window对象直接调用了 window.sayColor()函数,从而输出字符串。

    另一种获取 Global 对象的方式是使用如下的代码:

    let global = function() { 
        return this; 
    }();
    

    这段代码创建一个立即调用的函数表达式,返回了 this 的值。如前所述,当一个函数在没有明确(通过成为某个对象的方法,或者通过 call()/apply())指定 this 值的情况下执行时,this 值等于Global 对象。因此,调用一个简单返回 this 的函数是在任何执行上下文中获取 Global 对象的通用方式

4.4.2 Math

ECMAScript 提供了 Math 对象作为保存数学公式、信息和计算的地方。Math 对象提供了一些辅助计算的属性和方法。

Note: Math 对象上提供的计算要比直接在 JavaScript 实现的快得多,因为 Math 对象上的计算使用了 JavaScript 引擎中更高效的实现和处理器指令。但使用 Math 计算的问题是精度会因浏览器、操作系统、指令集和硬件而异

  1. Math对象属性

    Math 对象有一些属性,主要用于保存数学中的一些特殊值。下表列出了这些属性。

    属性说明
    Math.E自然对数的基数 e 的值
    Math.LN1010 为底的自然对数
    Math.LN22 为底的自然对数
    Math.LOG2E以 2 为底 e 的对数
    Math.LOG10E以 10 为底 e 的对数
    Math.PIπ 的值
    Math.SQRT1_21/2 的平方根
    Math.SQRT22 的平方根
  2. **min()**和 **max()**方法

    Math 对象也提供了很多辅助执行简单或复杂数学计算的方法。

    min()max()方法用于确定一组数值中的最小值和最大值。这两个方法都接收任意多个参数,如下面的例子所示:

    let max = Math.max(3, 54, 32, 16); 
    console.log(max); // 54 
    let min = Math.min(3, 54, 32, 16); 
    console.log(min); // 3
    

    在 3、54、32 和 16 中,Math.max()返回 54,Math.min()返回 3。使用这两个方法可以避免使用额外的循环和 if 语句来确定一组数值的最大最小值。

    要知道数组中的最大值和最小值,可以像下面这样使用扩展操作符

    let values = [1, 2, 3, 4, 5, 6, 7, 8]; 
    let max = Math.max(...val);
    
  3. 舍入方法

    接下来是用于把小数值舍入为整数的 4 个方法:Math.ceil()Math.floor()Math.round()Math.fround()。这几个方法处理舍入的方式如下所述。

    • Math.ceil()方法始终向上舍入为最接近的整数。
    • Math.floor()方法始终向下舍入为最接近的整数。
    • Math.round()方法执行四舍五入。
    • Math.fround()方法返回数值最接近的单精度(32 位)浮点值表示。

    以下示例展示了这些方法的用法:

    console.log(Math.ceil(25.9)); // 26 
    console.log(Math.ceil(25.5)); // 26 
    console.log(Math.ceil(25.1)); // 26 
    console.log(Math.round(25.9)); // 26 
    console.log(Math.round(25.5)); // 26 
    console.log(Math.round(25.1)); // 25 
    console.log(Math.fround(0.4)); // 0.4000000059604645 
    console.log(Math.fround(0.5)); // 0.5 
    console.log(Math.fround(25.9)); // 25.899999618530273 
    console.log(Math.floor(25.9)); // 25 
    console.log(Math.floor(25.5)); // 25 
    console.log(Math.floor(25.1)); // 25
    

    对于 25 和 26(不包含)之间的所有值,Math.ceil()都会返回 26,因为它始终向上舍入。Math.round()只在数值大于等于 25.5 时返回 26,否则返回 25。最后,Math.floor()对所有 25 和26(不包含)之间的值都返回 25。

  4. **random()**方法

    Math.random()方法返回一个 0~1 范围内的随机数,其中包含 0 但不包含 1。对于希望显示随机名言或随机新闻的网页,这个方法是非常方便的。可以基于如下公式使用 Math.random()从一组整数中随机选择一个数:

    number = Math.floor(Math.random() * total_number_of_choices + first_possible_value)
    

    这里使用了 Math.floor()方法,因为 Math.random()始终返回小数,即便乘以一个数再加上一个数也是小数。因此,如果想从 1~10 范围内随机选择一个数,代码就是这样的:

    let num = Math.floor(Math.random() * 10 + 1);
    

    这样就有 10 个可能的值(1~10),其中最小的值是 1。如果想选择一个 2~10 范围内的值,则代码就要写成这样:

    let num = Math.floor(Math.random() * 9 + 2);
    

    2~10 只有 9 个数,所以可选总数(total_number_of_choices)是 9,而最小可能的值(first_possible_value)是 2。很多时候,通过函数来算出可选总数和最小可能的值可能更方便,比如:

    function selectFrom(lowerValue, upperValue) { 
        let choices = upperValue - lowerValue + 1; 
        return Math.floor(Math.random() * choices + lowerValue); 
    } 
    let num = selectFrom(2,10); 
    console.log(num); // 2~10 范围内的值,其中包含 2 和 10
    

    这里的函数 selectFrom()接收两个参数:应该返回的最小值和最大值。通过将这两个值相减再 1 得到可选总数,然后再套用上面的公式。于是,调用 selectFrom(2,10)就可以从 2~10(包含)范围内选择一个值了。使用这个函数,从一个数组中随机选择一个元素就很容易,比如:

    let colors = ["red", "green", "blue", "yellow", "black", "purple", "brown"]; 
    let color = colors[selectFrom(0, colors.length-1)];
    

    在这个例子中,传给 selectFrom()的第二个参数是数组长度减 1,即数组最大的索引值。

    Note: Math.random()方法在这里出于演示目的是没有问题的。如果是为了加密而需要生成随机数(传给生成器的输入需要较高的不确定性),那么建议使用 window.crypto. getRandomValues()。

  5. 其他方法

    Math 对象还有很多涉及各种简单或高阶数运算的方法。下表总结了 Math 对象的其他方法。

    方法说明
    Math.abs(x)返回 x 的绝对值
    Math.exp(x)返回 Math.E 的 x 次幂
    Math.expm1(x)等于 Math.exp(x) - 1
    Math.log(x)返回 x 的自然对数
    Math.log1p(x)等于 1 + Math.log(x)
    Math.pow(x, power)返回 xpower 次幂
    Math.hypot(…nums)返回 nums 中每个数平方和的平方根
    Math.clz32(x)返回 32 位整数 x 的前置零的数量
    Math.sign(x)返回表示 x 符号的 1、0、-0 或-1
    Math.trunc(x)返回 x 的整数部分,删除所有小数
    Math.sqrt(x)返回 x 的平方根
    Math.cbrt(x)返回 x 的立方根
    Math.acos(x)返回 x 的反余弦
    Math.acosh(x)返回 x 的反双曲余弦
    Math.asin(x)返回 x 的反正弦
    Math.asinh(x)返回 x 的反双曲正弦
    Math.atan(x)返回 x 的反正切
    Math.atanh(x)返回 x 的反双曲正切
    Math.atan2(y, x)返回 y/x 的反正切
    Math.cos(x)返回 x 的余弦
    Math.sin(x)返回 x 的正弦
    Math.tan(x)返回 x 的正切

    即便这些方法都是由 ECMA-262 定义的,对正弦、余弦、正切等计算的实现仍然取决于浏览器,因为计算这些值的方式有很多种。结果,这些方法的精度可能因实现而异。

4.5 小结

JavaScript 中的对象称为引用值,几种内置的引用类型可用于创建特定类型的对象。

  • 引用值与传统面向对象编程语言中的类相似,但实现不同。
  • Date 类型提供关于日期和时间的信息,包括当前日期、时间及相关计算。
  • RegExp 类型是 ECMAScript 支持正则表达式的接口,提供了大多数基础的和部分高级的正则表达式功能。

JavaScript 比较独特的一点是,函数实际上是 Function 类型的实例,也就是说函数也是对象。因为函数也是对象,所以函数也有方法,可以用于增强其能力。

由于原始值包装类型的存在,JavaScript 中的原始值可以被当成对象来使用。有 3 种原始值包装类型:Boolean、Number 和 String。它们都具备如下特点。

  • 每种包装类型都映射到同名的原始类型。
  • 以读模式访问原始值时,后台会实例化一个原始值包装类型的对象,借助这个对象可以操作相应的数据。
  • 涉及原始值的语句执行完毕后,包装对象就会被销毁。

当代码开始执行时,全局上下文中会存在两个内置对象:Global 和 Math。其中,Global 对象在大多数 ECMAScript 实现中无法直接访问。不过,浏览器将其实现为 window 对象。所有全局变量和函数都是 Global 对象的属性。Math 对象包含辅助完成复杂计算的属性和方法。

### 回答1: 《JavaScript高级程序设计》第4是一本深入讲解JavaScript编程语言的书籍。该书详细介绍了JavaScript的基础知识、语法、面向对象编程、DOM操作、事件处理、Ajax、JSON等方面的内容。此外,该书还介绍了一些高级技术,如模块化编程、正则表达式、Web Workers、Web Storage等。该书适合有一定JavaScript基础的读者阅读,可以帮助读者深入了解JavaScript编程语言,提高编程技能。 ### 回答2: 《JavaScript高级程序设计》第4(以下简称《JS高级程序设计》)是由李炎恢编写的一部JavaScript语言的经典教材,它被誉为“JavaScript圣经”。本书全面深入地讲解了JavaScript语言的核心概念、高级特性和最佳实践,对于想要深入学习JavaScript的开发者来说是一本必读之作。 首先,本书从JavaScript的基础知识开始,包括JavaScript的数据类型、变量、运算符、函数等。随后,本书详细介绍了JavaScript的面向对象编程,包括对象、原型、继承等概念,以及使用构造函数和类来创建对象的方法。 其次,本书不仅讲述了JavaScript的基本语法,更详细深入地介绍了诸如函数表达式、闭包、高阶函数、递归等高级特性,对于想要提高自己的JavaScript编程能力的开发者很有帮助。 最后,本书也介绍了一些实际的开发技巧和最佳实践,例如DOM操作、事件处理、Ajax、JSON、模块化开发等,让开发者在实际的开发中更加得心应手。 总之,《JavaScript高级程序设计》第4是一本权威性的JavaScript经典教材,它涵盖了JavaScript的核心概念、高级特性和最佳实践,对于想要深入了解JavaScript的开发者来说是一本必读之作。无论是初学者还是有经验的开发者,都可以从中找到大量有用的知识和实践经验。 ### 回答3: 《JavaScript高级程序设计》第4是由三位著名的前端开发专家编写的JavaScript权威教程。本书介绍了JavaScript的核心概念、语言特性和应用方法,以及一些高级技巧和最佳实践。 本书的第一部分从JavaScript基础语法开始介绍,包括变量声明、数据类型、操作符、语句和函数等方面。第二部分主要介绍JavaScript的面向对象编程,包括原型链、继承和封装等概念。第三部分主要介绍JavaScript的一些高级特性,包括闭包、异步编程、事件和延迟加载等内容。第四部分主要介绍了如何使用JavaScript实现一些实际应用,包括调试、性能优化、动态Web页面和跨域请求等方面。 本书内容全面、深入,不仅介绍了JavaScript的基础知识,更重要的是让读者理解了JavaScript的思想和编程风格。编写本书的三位专家都是行业内的大牛,他们的经验和见解非常宝贵,能够帮助读者更好地理解JavaScript。同时,本书的配套网站还提供了很多实例代码和练习题,读者可以通过这些实践来深入理解JavaScript。 总之,《JavaScript高级程序设计》第4是一本非常不错的JavaScript权威教程,无论是初学者还是专业开发者都可以从中受益匪浅。如果你想深入学习JavaScript,这本书绝对值得一读。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值