js语言精粹一书整理

##表示每一章,####表示每一个小节
书 P35的递归方法、书55页部件

注:字面量表示如何表达这个值,一般除去表达式,给变量赋值时,等号右边的就可以认为是字面量
jq的核心思想是单例、工厂模式;vue,react的核心思想是观察者、MVVM模式。

var test = "hello world!"

这里 “hello world!”就是字面量,test是变量名
在js中,字面量分为字符串字面量,对象字面量,数组字面量和函数字面量

##补充

  • js中的apply和call的区别,两个方法的第一个参数都是当前调用的函数的调用环境(this),而apply是将所有参数放在一个数组中,但是会自动拆分(只会拆分一次)。而call是有多少个参数就放多少参数,不会自动拆分

##第二章 语法

####语句

  • 在js中会被判断为假的字面量

    1. false
    2. null
    3. undefined
    4. 空字符串’’
    5. 数字0
    6. 数字NaN

    ####表达式

  • 如果函数没有写reture,那么会默认返回undefined
  • typeof 会产生的类型 number,string,boolean,object,function,undefined,但是当时一个数组或者使null时,返回的是object,这其实是不对的
  • 对象字面量就是指包围在一个花括号中的零或多个键值对,对象字面量可以出现在任何允许表达式出现的地方
  • 检索对象里面包含的值bookinfo[“item”]

##第三章 对象

####检索

  • javascript的标识符中不能包含连接符(-),但是可以包含下划线(_)
  • 一个机智的写法 bookinfo.item && bookinfo.item.child,不会因为item属性不存在而抛出异常

####引用

  • 对象通过引用传递,对象永远不会被复制

####原型

  • 原型连接在更新时是不起作用的,所以对某个对象作出改变时,不会触及该对象的原型
  • 委托可以理解为,在一个对象中找某个属性时,如果这个对象没有,那么在它的原型中继续找,如果也没有,那么继续往上找,直到object.prototype
  • 当我们添加一个新的属性到原型中,那么所有基于这个原型创建的对象对该属性都会可见
  • hasOwnProperty方法不会检查原型链,只会检查该对象是否独有这个属性

####减少全局变量的污染

  • 全局变量削弱了程序的灵活性,应该避免使用,最小化全局变量的方法就是只创建一个唯一的全局变量,这样与其他应用程序或者页面冲突的可能就会明显降低,还有一种有效减少全局变量的方式方式就是闭包。

    var mappy = {};
    

##第四章 函数

####函数对象和字面量

  • 函数对象连接到Function.property(改原型对象本身也连接到object.property)
  • js的函数被创建时,会有一个调用属性,当你调用函数时,其实是调用了这个‘调用’属性
  • 通过函数字面量创建的函数对象包含一个连到外部上下文的连接,这被称为闭包

####函数的调用方式

  • 函数的四种调用模式,这四种模式在如何初始化关键参数this上存在差异
    1. 方法调用模式,可以理解为在对象中的属性,这个时候就不是函数,而是方法了,此时this指向当前的对象
    2. 函数调用模式,此时this会绑定到全局(这是语言设计上的一个错误),需要定义一个that变量来存储之前的this
    3. 构造器调用模式,使用new来调用,那么就会创建一个连接到该函数property成员的新对象,同时,this也会绑定到这个新对象上
    4. apply调用模式,需要两个参数,一个表示要绑定的this值,一个表示传递的参数数组,this传null默认绑定到window变量
      四种调用模式

####返回

  • 函数调用时若用了new,则返回当前的this(该新对象),若不用new 调用,且函数没有return的话,则返回undefined

####异常

  • 在类库混用时,可以用异常进行确保没有该方法时才进行添加

####作用域

  • js不支持块级作用域(任何一对花括号中的代码{}都是一个块,在js中,在块内用var声明的变量可以在块外访问到,但是在es6中,let和const则是支持块级作用域的,let其实也是支持变量提升的,只是提升到函数的顶部,但是没有初始化为undefined,所以在提升的顶部到变量初始化之前的区域都不能使用,这就是暂时性死区),所以使用var的话,最好就是在函数顶部吧变量都声明
  • 避免在循环中创建函数,这样只会带来无意义的计算并且引起混淆

####模块

  • 模块是一个提供接口却隐藏状态与实现的函数或对象(有点类似java类的私有变量)
  • 可以使用函数和闭包来构建模块
  • 模块模式的一般形式:定义了私有变量和函数的函数,利用闭包创建可以访问变量和函数的特权函数,最后返回这个特权函数即可。
  • 通过模块,几乎可以完全摒弃全局变量的使用。(javascript最糟糕的特性之一 ———— 全局变量)

####级联

  • element.width(‘100px’).height(‘100px’)就是一个级联,每次调用的结果都会被下一次所调用,所以没有必要写太过全能的接口,一个接口没必要一次做太多的事情。

####记忆

  • 将先前操作的结果记在某个对象里,从而避免重复的计算
    example

##第五章 继承

  • proto: 这个属性是实例对象的属性,每个实例对象都有一个proto属性,这个属性指向实例化该实例的构造函数的原型对象(隐示原型,通过这个属性增加方法的话,需要添加到下面的constructor属性里添加)。
  • proterty:这个方法是对象的属性。(据说和一个对象的attr类似,比如dom对象中)

  • prototype:每个构造函数都有一个prototype对象,这个对象指向该构造函数的原型。(显示原型)

  • 常用的几种继承方式
    1. 伪类,基于类继承,通过修改子类的prototype去继承父类的属性和方法(在子类的prototype上增加方法和属性不会修改父类的)
    2. 基于原型的继承(不会对父原型改变)
      example
    3. 使用函数化实现集成,运用块模式,实现变量的私有化
      example

第六章 数组

  • js中没有数组,但是js有一些类数组也行的对象

长度

  • js中的数组的length是没有上届的,若用大于当前数组长度的下标去存储,那么当前数组会自动拓展到那个长度

    删除

  • 使用delete删除之后会在原来的位置保留一个空洞 [1, empty, 1, 1, 1]
  • 使用splice对数组进行删除,但是这对于大型数组的效率不高

    容易混淆的地方

  • js中对于数组和对象的区分并不严格,当属性名是小而连续的时候,应该使用数组,否则,使用对象。
  • typeof 数组,结果是‘object’,所以需要自己实现对于当前对象是否是数组的判断
  • 使用arr.totle=function(){}是可以的而且不会改变arr的length
  • 使用Object.create()方法对数组是没用的,因为复制出来的对象而不是数组

第七章 正则表达式(本章基本跳过,没有深入学习)

  • 可处理正则的几个表达式regexp.exec,regexp.test,string.atch,string.replace,string.search和string.split
  • 正则表达式的标识
    1. g 全局匹配,不通的方法对g标识的处理不太一样,regExp的exec方法和test方法,不建议使用test方法,再比如string的search和match方法,search方法会忽略g
    2. i 大小写不敏感
    3. m 多行(^和$匹配行结束符)
  • 分支,使用|表示分支,会按顺序进行匹配,只要匹配一个就表示匹配成功

第八章 方法

Array

  • array.concat(item…),产生一个新数组,包含一份array的浅复制,后面的item会被依次添加
  • join
  • array.pop()使得数组像堆栈一样工作,移除数组的最后一个元素并且返回,如果没有最后一个,那么返回undefined
  • push 该方法会返回数组的length
  • array.reverse()反转数组并且返回
  • array.shift()删除数组的第一个元素,并且返回该元素,如果为空,那么返回undefined,shift通常比pop慢得多(因为删除第一个并且要讲剩下的元素顺序前移)
  • array.slice(start,end)字符串的截取,如果其中任何一个是负数的话,那么array.length会和他们相加,如果还是负数,那么返回一个新的空数组,注意个splice方法的区分,直接写一个负数表示取到最后几位
  • array.sort()数组的排序,但是因为排序之前会先转换成字符串,所以不能正确的对数字数组进行排序,当然,你可以自己重写排序方法内的逻辑
    自带排序和自己重写排序的对比
    运行结果
  • 对象的比较见书P80
  • splice(start,count,item….)删除之后,item会替换原来删除位置的元素
  • array.unshift(item…)插入元素到数组的开头,并且返回length

    String

  • chartAt(pos)返回pos处的字符,如果没有,返回空
  • charCodeAT
  • concat
  • indexOf
  • lastindexOf
  • localeCompare(that)类似array.sort,比较两个字符串,小于为-1,等于为0,大于为1
  • match(regexp)根据正则表达式进行匹配,根据g表示决定如何匹配
  • replace
  • replaceAll
  • search(regexp)类似indexof,只是接受的是一个正则表达式
  • slice(start,end)复制字符串从start到end-1位置的字符,和array.slice类似,没有则返回一个新的空字符串
  • split(separator,limit)根据separator对string进行分组,limit是可选参数,表示分组的限制
  • substring(start,end)和slice一样,但是不能支持负数,所以建议使用slice,不建议使用substring
  • toLocalLowerCase()用本地规则吧string中的字母转换成小写,主要是用于土耳其语‘I’转换为‘1’
  • toLocalUpperCase()用本地规则吧string中的字母转换成大写,主要是用于土耳其语‘i’转换为向量I
  • toLowerCase()转为小写
  • toUpperCase()转为大写
  • formChartCode(item….)将一串数字编码(Ascii)返回为一个字符串

##代码风格(代码不光是给计算机运行的,也是给人看的)

  • if while 这样的结构化语句中,要始终使用代码块,很有可能if(a)b;变成了if(a)b;c;一对花括号可以用低廉的成本去防止可能出现的BUG
  • 使用K&R风格,吧{放在一行的结尾而不是下一行的开头,这样可以避免js return语句的一个可怕的设计错误
  • K&R风格
    1. 在合适的位置换行(比如在一个超长的表达式中,在两个表达式之间断开)
    2. 在二元运算符的位置换行也是可行的
    3. 当参数较多或者参数较长时,多行往往比一行更加直观醒目
    4. {}写紧凑,{在一行的末尾,}也是
    5. 多个不同意义但是相同类型的变量最好分开定义和注释
    6. 代码不要过于拥挤,注意添加空行
    7. 弹幕运算符一般不需要空格
    8. 双目,三目运算符的操作数之间要加上运算符
    9. 代码要保持好缩进,保证可读性
  • 避免那些看起来可能有错误的语法(变量提升,if(a=b)等等)
  • 减少对于全局变量的使用,随着程序的日益复杂,全局变量的问题会逐渐变得问题重重

##js中的毒瘤

全局变量

  • 全局变量
    1. js中的全局变量是所有程序都可以随时访问和修改的,这是一个很大的问题,也很容易产生BUG
    2. 当两个程序中的全局变量产生了冲突时,会产生BUG,而且这样的问题很难被发现
    3. js中所有的全局变量在编译后都将载入一个公共的全局变量中
    4. 定义全局变量的三种方法 var foo = value; window.foo = value; foo = value
      ####作用域
  • 虽然js是类C语言,但是没有块级作用域,也就是说,在{}外可以访问到{}内定义的变量,但是在es6中let,const可以有效避免这样的问题
    ####(使用模块设计可以有效避免上面的两个问题,减少全局变量的污染和作用域的问题,同时还能提供私有变量)
    ####自动插入分号
    js中存在自动补全分号的机制,但是别指望他能干什么好事,很多情况下都是产生难以检查的BUG

####保留字
当用保留字进行变量的定义时,需要使用引号

####typeof
typeof null //object
typeof value //value为数组,结果为object

####parseInt
parseint将字符串转为int,但是遇到非数字时就会停止,且当为0开头时自动转换为8进制所以当使用这个函数时,最好手动指定转换的进制parseInt(value,10)

+

+号会自动对两个操作数进行类型转换,所以当进行+操作时,请确保两个操作数都是数字

####NaN

NaN === NaN //false
NaN !== NaN //true
  • 使用isNaN函数对数字与NaN进行辨别
  • 使用isFinite函数对是否是数字进行判定,但是它会自动将运算数转为数字
  • 自定义isNumer函数

    var isNumber = function(value){
        return typeof value === 'number' && isFinite(value)
    }
    

伪数组

  • 要检测一个对象是否是数组,使用typeof是不够的还需要检查他的constructor属性

    //最保险的做法
    if(Object.prototype.toString.apply(my_value) === '[object Array]'){
        //my_value确实是一个数组
    }
    
  • 函数自带的arguments参数不是一个数组对象,只是一个有着length成员属性的对象

####假值

  • js中的假值
    1. 0
    2. NaN
    3. ‘’
    4. false
    5. null
    6. undefined
  • undefined 和 null并不是常亮,但他们是全局变量(在es5中明确规定为全局变量),在主流的浏览器中都无法改变他们的值,但是ie8及以下可以

####hasOwnProperty
不要替换hasOwnProperty的值

####对象

js中因为原型的存在,所有永远不会有空对象,所以在某些情况下判断属性的时候需要注意

##附录B 糟粕

####==
避免使用==,他会自动对比较的两遍进行转换,比对出很对莫名其妙的结果,这会让你的程序变的有些不可控

####with
和==一样,他会产生一些不可控的结果,所以避免使用

####eval
传递一个字符串给JS编辑器,并执行其结果,使用它会让代码变的难以阅读,因为它需要运行编译器,而且会让jsLint失效,并且会减弱程序的安全性,因为它给了被求值的文本太多的权利,而且还降低了语言的性能,在setTimmeout和setInterval函数中,若传递的是字符串是,会像eval一样去处理,同样也要避免这样的参数传入

####continue
经过测试,代码重构之后,将continue去除,都会使代码的性能得到改善

####缺少块语句
在if while for语句中,尽管只有一行逻辑,也要把{}加上,防止逻辑出错

####++ –
这两种运算符鼓励了一种不够严谨的代码风格,大多数缓冲区溢出错误所造成的安全漏洞都是这样导致的
在实践中,使用++和–时,往往会导致代码过于拥挤,复杂和隐晦,这样会让代码变的难以阅读和理解,所以,可以尽量避免使用他们

####位运算符
大多数语言中,位运算符接近于硬件处理,所以非常快,但是js中很少接触到硬件,由于js中没有整数类型,所以位运算符会先转换成整数进行运算再转换回去,而且由于js接触不到硬件,所以位运算符非常慢

####function语句和function表达式
语句 function test(){} 表达式 var foo = function foo(){}

function语句在解析时会发生被提升的情况,会被移动到定义时所在的作用域的顶层。这样可能会导致混乱

一个语句不能以一个函数开头,包裹在()之中就可以了(function())

####类型包装对象

new Boolean(false)会返回一个对象,该对象的valueOf方法会返回包装对象的值,这完全没有必要,包括其他基本类型的new也应该避免

new

如果应该是用new初始化而没有的话,那么就是声明一个普通的对象,而且对象中的this就会绑定到全局变量,这样会污染全局变量

一般使用大写开头来区分构造器函数,但是最好避免使用new来引发混乱

####void
void在js中是一个运算符,接受一个运算数并且返回undefined,这并没有什么用,但会令人困惑