zoukankan      html  css  js  c++  java
  • 阅读笔记《JavaScript语言精粹》

    阅读笔记《JavaScript语言精粹》

    对象

    1.检索属性

    使用[]和.

    2.引用传递

    JavaScript的简单数据类型包括数字、字符串、布尔值、null值和undefined值。其它所有的值都是对象。数组是对象,函数是对象,正则表达式是对象。对象通过引用传递,它们永远不会被复制。

    3.原型

    当我们对某个对象做出改变时,不会触及该对象的原型,只有在检索值的时候才会被用到。原型连接在更新时是不起作用的。delete删除对象中的属性,它也不会触及原型链中的任何对象,删除对象的属性可能会让来自原型链中的属性透现出来。

    4.枚举

    for-in语句枚举过程会列出所有的属性——包括函数和你可能不关心的原型中的属性——所以有必要过滤掉那些你不想要得值。最为常用的过滤器是hasOwnProperty方法,以及使用typeof来排除函数。

    5.减少全局变量污染

    JavaScript依赖于全局变量来进行链接,应当把全局性的资源都纳入一个名称空间之下,你的程序与其它应用程序、组件或类库之间发生冲突的可能性就会显著降低。


    函数

    1.函数对象

    函数对象连接到Function.prototype(该原型对象本身连接到Object.prototype),每个函数在对象创建时会附加两个隐藏属性:函数的上下文和实现函数行为的代码。函数的与众不同之处在于它们可以被调用。

    2.闭包

    函数可以定义在其他函数中。一个内部函数除了可以访问自己的参数和变量,同时它也能自由访问把它嵌套在其中的父函数的参数和变量。通过函数字面量创建的函数对象包含一个连到外部上下文的连接。这被称为闭包。

    3.调用

    除了声明时定义得形式参数,每个函数还接收两个附加的参数:this和arguments。参数this的值取决于调用的模式,这些模式在如何初始化关键参数this上存在差异。

    • 方法调用模式:当函数为对象的一个属性时,称它为该对象的方法,this绑定到该对象,方法可以通过this访问该对象的属性。

    • 函数调用模式:当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的,以此模式调用函数时,this被绑定到全局对象。这是语言设计上的一个错误。(应当绑定到外部父函数的this变量。)

    • 构造器调用模式:如果在一个函数前面带上new来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时this会被绑定到那个对象。new前缀也会改变return语句的行为。(参照5.返回)

    • apply调用模式:函数是对象,所以也拥有方法。apply方法让我们构建一个参数数组传递给调用函数。apply接收两个参数,第一个是要绑定给this的值,第二个就是一个参数数组。

    4.参数

    当函数被调用时,会得到一个arguments参数,可以通过它访问函数所有的参数列表,这使得编写一个无须指定参数个数的函数成为可能。arguments拥有一个length属性,但它不是数组,并没有任何数组的方法。

    5.返回

    一个函数总会有返回值,如何没有指定就返回undefined。如果函数调用时前面加上了new前缀,且返回值不是一个对象,则返回this(该新对象)。

    6.扩充类型的功能

    通过给基本类型增加方法,我们可以极大的提高语言的变现力。因为JavaScript原型继承的动态本质,新的方法立刻被赋予到所有的对象实例上,哪怕对象示例是在方法被增加之前就创建好了。object.prototype.METHOD_NAME = ...

    7.递归

    递归函数可以非常高效的操作树形结构,遗憾的是,javas当前并没有提供递归优化。深度递归的函数可能会因为堆栈溢出而运行失败。

    8.作用域

    JavaScript不支持块作用域,只有函数作用域,意味着定义在函数中的参数和变量在函数外部不可见,而在内部任何位置定义的变量,在该函数任何地方都可见。所以,最好在函数体的顶部声明函数中可能用到的所有变量。

    9.模块

    使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象,通过使用函数产生模块,我们几乎可以完全摒弃全局变量的使用。它促进了信息隐藏和其他优秀的设计实践。
    模块模式的一般形式:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有化变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可以访问到的地方。

    10.级联

    级联技术可以产生出极富表现力的接口。它也能给那波构造“全能”接口的热潮降温,一个接口没有必要一次做太多事情。

    obj.setPositionX(10)
       .setPositionY(10)
       .color("red")
       .border("10px")
       .tip("级联");
    

    11.记忆

    函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算。这种优化被称作记忆。JavaScript的对象和数组要实现这种优化非常的方便。比如用递归计算Fibonacci数列...


    继承

    JavaScript是弱类型的语言,从不需要类型转换。对象继承关系变得无关紧要。对于一个对象来说重要的是它能做什么,而不是它从哪里来。
    在基于类的语言中,对象是类的示例,并且类可以从另一个类继承。JavaScript是一门基于原型的语言,这意味着对象直接从其他对象继承。
    JavaScript中可能的继承模式有很多。

    1.伪类

    使用new前缀。伪类模式本意是想向面向对象靠拢,但它看起来格格不入。

    2.原型

    原型模式中会摒弃类,转而专注于对象,一个新对象可以继承一个旧对象的属性。

    3.函数化(模块)

    函数化模式有很大的灵活性。它相比伪类模式不仅带来的工作更少,还让我们得到更好的封装和信息隐藏,以及访问父类方法的能力。

    4.部件

    我们可以从一套部件中把对象组装出来。例如,我们可以构造一个给任何对象添加简单事件处理特征的函数。


    数组

    数组是一段线性分配的内存,它通过整数计算偏移并访问其中的元素。数组是一种性能出色的数据结构。不幸的是,JavaScript没有像此类数组一样的数据结构。
    作为替代,JavaScript提供了一种拥有一些类数组特征的对象。它把数组的下标转换成字符串,用其作为属性。它明显地比一个真正的数组慢,但它使用起来很方便。它的属性检索和更新与对象一模一样,只不过多一个可以用整数作为属性名的特性。

    1.长度

    每个数组都有一个length属性。和大多数其它语言不同,JavaScript数组的length是没有上界的。如果用大于等于当前length的数字作为下标来存储一个元素,那么length值会被增大以容纳新元素,不会发生越界错误。length属性的值是这个数组的最大整数属性名加上1:

    var arr = [];
    arr[100] = true;
    //arr.length  === 101
    

    设置更大的length不会给数组分配更多的空间。而把length设小将导致所有下标大于等于新length的属性被删除。

    2.删除

    由于JavaScript的数组其实就是对象,所以delete运算符可以用来从数组中移除元素。

    var arr = [10,20,30];
    delete arr[1];
    // arr: [10, undefined, 30]
    

    不幸的是,那样会在数组中留下一个空洞。因为前排的在被删除元素之后的元素保留着它们最初的属性。而你通常想要递减后面每个元素的属性。
    JavaScript数组有一个splice方法用来进行数组元素的增加删除,对于大型数组来说可能会效率不高。

    3.判断是否数组

    JavaScript本身对于数组和对象的区别是混乱的。typeof运算符报告数组的类型是'object',这没有任何意义。我们可以通过is_array函数来弥补这个缺陷。

    var is_array = function (value) {
    	return value && typeof value === 'object' && value.constructor === Array;
    };
    

    遗憾的是,它在识别不同的窗口(window)或帧(frame)里构造的数组时会失败。有一个更好的方式去判断一个对象是否为数组:

    var is_array = function (value) {
    	return Object.prototype.toString.apply(value) === '[Object Array]';
    };
    

    代码风格

    1. 优秀的程序拥有一个前瞻性的结构,它会预见到未来才可能需要的修改,但不会让其成为过度的负担。
    2. 使用K&R风格,把 { 放在一行的结尾而不是下一行的开头,因为它会避免JavaScript的return语句中的一个可怕的设计错误。(插入分号)
    3. JavaScript拥有C语言的语法,但它的代码块没有作用域。所以在每个函数的开始部分声明所有的变量。
    4. JavaScript可以方便的使用全局变量,但随着程序的日益复杂,全局变量逐渐变得问题重重。对一个脚本应用或工具库,尽量只使用唯一一个全局变量。每个对象都有它自己的命名空间。使用闭包能提供进一步的信息隐藏。

    毒瘤

    展示JavaScript一些难以避免的问题特性。你必须知道这些问题并准备好应对措施。

    1.全局变量

    全局变量可以被程序的任何部分在任意时间修改,它们使得程序的行为变得极度复杂。在程序中使用全局变量降低了程序的可靠性。
    全局变量使得在同一个程序中运行独立的子程序变得困难。如果某些全局变量的名称碰巧和子程序中的变量名称相同,那么他们将会相互冲突,可能导致程序无法运行,而且通常难以调试。
    JavaScript的问题不仅在于它允许使用全局变量,而且在于它依赖全局变量。JavaScript没有连接器,所有的编译单元都会载入一个公共的全局对象中。
    共有3种方式定义全局变量:

    • 第1种是在任何函数之外放置一个var语句:
    var foo = value;
    
    • 第2种是直接给全局对象添加一个属性。全局对象时所有全局变量的容器。在Web浏览器里,全局对象名为window:
    window.foo = value;
    
    • 第3种是直接使用未经声明的变量,这被称为隐身的全局变量:
    foo = value;
    

    2.作用域

    JavaScript的语法来源于C。它采用了块语法,却没有提供块级作用域:代码块中声明的变量在包含此代码块的函数的任何位置都是可见的。所以应该在每个函数的开头部分声明所有的变量。

    3.自动插入分号

    JavaScript有一个自动修复机制,它试图通过自动插入分号来修正有缺损的程序。但是,千万不要指望它,它可能会掩盖更为严重的错误。
    return返回一个值时,这个值表达式的开始部分必须和return位于同一行,否则return后面被插入分号后,返回结果将是undefined。

    4.Unicode

    JavaScript设计之初,Unicode预期最多会有65536个字符。但从那以后它的容量慢慢增长到了拥有一百万个字符。
    JavaScript的字符是16位的,足以覆盖原有的65536个字符。剩下的百万字符中的每一个都可以用一对字符来表示。Unicode把一对字符视为一个单一的字符。而JavaScript认为一对字符是两个不同的字符。

    5.typeof

    typeof null返回的是'Object',而不是null
    对于正则表达式typeof /a/各种JavaScript的实现版本不太一致

    6.parseInt

    parseInt把一个字符串转换为整数,它在遇到非数字时会停止解析。
    parseInt("16")与parseInt("16 haha")会产生相同的结果,它不会提醒我们出现了额外的文本。

    7.+运算符

    +运算符用于加法运算或字符串连接。如果其中一个运算数是一个(空)字符串,它会把另一个运算数转换成字符串并返回。如果你打算用 + 去做加法运算,请确保两个运算数都是Number。这个复杂的行为是bug的常见来源。

    8.NaN

    typeof NaN === 'number' //true
    但是NaN表示的不是一个数字,该值可能会在试图把非数字形式的字符串转换为数字时产生。
    typeof不能辨别数字和NaN,而且NaN也不等同于它自己:

    NaN === NaN //false
    NaN !== NaN //true
    isNaN(NaN) //true
    isNaN(0) //false
    isNaN("0") //false
    isNaN("haha") //true
    

    9.伪数组

    arguments不是一个数组,它只是一个有着length成员属性的对象。
    检测数组:

    if(Object.prototype.toString.apply(VALUE) == "[object Array]") {
    	//VALUE是一个数组
    }
    

    10.假值

    //值 (类型)
    0         //Number
    NaN 	  //Number
    ""   	  //String
    false     //Boolean
    null      //Object
    undefined //Undefined
    

    undefined和NaN并不是常量。它们居然是全局变量,而且你可以改变它们的值。千万不要这样做。

    11.hasOwnProperty

    hasOwnProperty方法可以用做一个过滤器去避开for in语句中的一个隐患。遗憾的是,它是一个方法,而不是一个运算符,所以在任何对象中,它可能会被一个不同的函数甚至一个非函数的值所替换。

    11.对象

    JavaScript的对象永远不会是真的空对象,因为它们可以从原型链中取得成员属性。有时候那会带来麻烦。


    糟粕

    展示JavaScript一些有问题的特性,但我们很容易就能避免它们。

    1.==

    如果两个运算数类型不同,和!=会强制转换运算数值的类型。永远不要使用它们!
    使用
    =和!,只有在两个运算数类型相同且拥有相同值时=才会返回true,!==返回false。

    2.with语句

    with语句本意是用来快捷访问对象的属性,不幸的是,它的结果可能有时不可预料,所以应该避免使用它。且它本身就严重影响了JavaScript处理器的速度,因为它阻断了变量名的词法作用域。

    3.eval

    eval使得代码更加难以阅读。还使得性能显著降低,因为它需要运行编译器,还会让JSLint无法检测其中的问题。
    浏览器提供的setTimeout和setInterval函数,参数传人字符串时也会调用eval。

    4.continus语句

    continue语句跳到循环的顶部,通过移除continue语句之后的代码,性能都会得到改善!

    5.switch穿越

    不要使用switch穿越,这会让我们更加容易发现不小心造成的case条件穿越导致的bug。

    6.缺少块的语句

    if, while, do 或 for 语句可以接受一个括在花括号中的代码,也可以接受单行语句。但那会模糊了程序的结构,不要这么做。

    7.位运算符

    JavaScript的执行环境一般接触不到硬件,所以非常慢。JavaScript很少被用来执行位操作。

    8.function语句

    不要在if中使用function语句,不同的浏览器在解析时的处理是不同的,这就会造成可移植性的问题。
    一个语句不能以函数表达式开头,因为官方的语法假定单词function开头的语句是一个function语句。解决方法是把函数调用括在一对圆括号中:

    (function () {
    	//
    }());
    

    9.类型的包装对象

    JavaScript有一套类型的包装对象,例如:new Boolean(false)会返回一个对象,该对象有一个valueOf方法会返回被包装的值。这其实完全没有必要,并且又是还令人困惑。
    不要使用new Boolean, new Number, new String, 也请避免使用new Objectnew Array,可使用 {} 和 [] 来代替。

    10.new

    使用new创建原型的新对象时,会涉及到this的绑定问题,如果在创建对象是忘记加上new,则会把this绑定到全局对象,造成全局变量污染。所以尽量避免去使用new。

    11.void

    在JavaScript中void是一个运算符,它接受一个运算数并返回undefined。这没什么用,且令人困惑。应避免去使用它。

  • 相关阅读:
    iOS中的两种主要架构及其优缺点浅析
    iOS
    iOS开发人员不容错过的10大工具
    安装CocoaPods报错
    把你唱的歌用乐器表达出来

    String.Split函数
    四部和声
    SerializeField和HideInInspector
    十年许嵩雅俗共赏
  • 原文地址:https://www.cnblogs.com/songcf/p/4954350.html
Copyright © 2011-2022 走看看