- 声明
- 特点
- 存值之前是undefined
- 声明提前
- 非声明变量赋值创建全局变量 # ECMAScript 5 严格模式下未声明赋值会报错
- 非声明的全局变量与声明全局变量的区别: 非声明的全局变量是可配置的(可delete),而var声明的全局变量不可配置
- let
- 支持了直接用{}的块级作用域
- 只在块级作用域有效
- 无变量提升
- 声明前存在暂时性死区
- 重复声明报错
- 声明的全局变量不再是全局对象的属性
- const
- var x;
- x = 1;
- let a = 10
- const PI = 3.1415
- export const A = 1;
- import * as constants from ‘./constants’
- constants.A
- 类型
- 特点
- 可以拥有方法的类型, 不能拥有方法的类型
- 可变(mutable)类型 # 值可以改变, 比较(, =)是地址的比较
- 不可变(immutable)类型 # 比较(, =)是值的比较
- 数字、布尔值、null、undefined、字符串 # 字符串不像c语言可以看作字符数组,js的字符串不可变
- 原始值
- 不可以拥有自己的方法
- null
- undefined
- 原始类型(primitive type)
- 可以拥有自己的方法, 原始类型都包含内置构造函数
- 数字
- 字符串
- 布尔值
- Symbol
- 对象类型(object type)或引用类型,如
- 对象(object)是属性(property)的集合
- 全局对象(global object)
- 数组类: Array
- 函数类: Function
- 类型转换 # Symbol不可转换
- 转换为数字 # 字符串允许在开始和结尾处带有空格
- false为 0
- true为 1
- ""为 0
- 失败为 NaN
- 转换为字符串
- 0 为”0”
- Infinity 为”-Infinity”
- [9]为 “9”
- [‘a’]其他数组,调用join()方法
- 对象转换字符串 # 运算符+ == != 的处理运用这里的原理
- 日期对象有自己的重定义,valueOf与toString返回的原始值将直接使用
- toString()
- valueOf() # 没有toString()方法时调用, 如果返回原始值,自动将它转换为字符串
- 数组、函数、正则表达式返回对象本身
- 日期类返回毫秒数
- 无法得到原始值则抛出异常
- 对象转换数字 # 运算符 - < 用了这里的原理
- 首先尝试valueOf() # 如果返回原始值,自动转换为数字
- 再尝试toString() # 自动转换为数字
- 无法得到原始值则抛出异常
- 例子
- 数字
- []为0 # valueOf → toString → "" → 0
- [9]为9 # valueOf → toString → “9” → 9
- 相等性
- null == undefined
- “0” == 0
- 0 == false
- “0” == false
- 类型
- 数字
- 基础
- 所有数字用二进制浮点数表示(64位, 如java中的double)
- IEEE-754标准
- 整数范围 -2^53 ~ 2^53(大约900亿亿)
- 实际的操作(数组索引, 位操作符)基于32位整数
- 负号是一元运算符,并不是数字直接量语法的组成部分
- 0与-0唯一差别
- 1/zero !== 1/-0 # 正无穷大和负无穷大不等
- 实数近似表示(几乎所有现代编程语言都存在, 因为都是IEEE-754标准)
- 浮点数表示褛的个数为18 437 736 874 454 810 627个
- IEEE-754标准精确表示1/2, 1/8, 1/1024,但不精确表示1/10, 1/00。
- 建议用大整数进行重要计算(如元, 角, 分各用整数表示)
- 所以js只能近似表示0.1
- var x = .3 - .2
- var y = .2 - .1
- x == y // ⇒ false
- x == .1 // ⇒ false
- y == .1 // ⇒ true
- 字符串
- 基础
- 从0开始
- 没有如c语言中的字符型
- 采用UTF-16编码的Unicode字符集。
- 是一组无符号16位值组成的序列。
- 用16位内码表示, 表示一个单个字符
- 不能16位Unicode表示的遵循UTF-16编码规则,两个16位值来表示一个(代理项对)
- 长度为2的字符串可能表示一个Unicode字符,如var e =“\ud835\udc52”; e.length // ⇒ 2
- 字符串的操作不对代理项对单独处理
- 不对字符串做标准代加工
- 运算
- unicode
- 允许采用\uxxxx表示\u0000 到 \uFFFF之间的字符
- 超出范围时用4字节表示, 如 \uD842\uDFB7
- ‘\u20BB7’ 会被解释成 ‘\u20BB’ + ‘7’
- ‘\u{20BB7}’ 会正确解释
- 多种表示法
- ‘\z’
- ‘\172’
- ‘\x7A’
- ‘\u007A’
- ‘\u{7A}’
- 转义
- 十六进制数表示Latin-1或Unicode中的任意字码, 由两位十六进制数指定
- \u表示4个十六进制数指定的Unicode字符
- \n
- ’
- \0 # 同\u0000, 表示空字符串
- \b # 同\u0008, 表示退格符
- \t # 同\u0009, 表示tab
- \v # \u000B, 垂直制表符
- \f # \u000C, 换页符
- \r # \u000D, 回车
- 布尔
- 转换
- 所有值都可以转换为布尔值
- false
- undefined
- null
- 0
- 0
- NaN
- ""
- true
- api
- toString() # 转换成”true”或”false”
- null undefined
- 类型
- null为object # 但可以表示数字类型、字符串类型
- undefined为”undefined”, 是一个单独类型
- 比较
- null == undefined // ⇒ true
- null === undefined // ⇒ false
- 无api # .和[]取成员会产生类型错误
- bug
- undefined在ECMAScript可读/写,可赋任意值
- 结论
- undefined表示系统级类似错误的空缺
- null表示程序级的,正常出现的空缺
- Symbol
- 介绍
- 原始数据类型,因为不是对象,所以不能new, 不能添加属性
- 不参与类型转换, 但可以toString # 可以String(s)得到 ‘Symbol(a)’, Boolean(s)得到true, !s 得到false。Number(s)会报错
- 可以做属性名a[sym1] = 1, 不能用点运算符赋值或取值
- 常用于设置常量结构体来switch,以消除魔术字符串
- 作用域
- 全局变量
- 就是定义顶级对象的属性 # 这点在ECMAScript规范中强制规定
- 在js代码任何地方都有定义
- 局部变量
- 与c语言区别(嵌套作用域)
- c中{}内产生块级作用域(block scope), 其中变量其外不可见
- js中没有块级作用域,是函数作用域(function scope), 变量在内部嵌套函数中有定义。
- 声明提前
- 内部嵌套函数而言, 变量声明之前就可用, 称为声明提前(hoisting) # hoisting js函数里声明的所有变量(不赋值), 被”提前”到函数体顶部,在js引擎预编译时进行。
- 例子
-
var scope = "global"
function f(){
console.log(scope) # undefined, 因为局部scope声明提前,覆盖了全局scope, 而声明提前不定义, 定义在执行代码时进行
var scope = "local" # 等价于开头var scope;
console.log(scope)
}
- 特点
- js本身设计中没有构造函数,普通函数,对象方法,闭包。这些都是莫须有的叫法
- 内部函数可以访问外部函数上下文
- 非严格格式直接声明变量,挂到global上
- 作用域在函数中定义, 非块定义, 所以
-
for(var i = 0; i < 10; i++){ # 中定义的i与value,在for之外可以被访问, 且声明提前
var value = 'hello';
}
- this
- 有调用对象就指向调用对象
- 没调用对象指向全局对象
-
O.a = function(){
var b = function(){
# b中的this永远是全局对象
console.log(this);
};
b();
};
O.a()
- new 构造时this指向新对象
-
var O = function(){this.val = 100;}
var o = new O();
console.log(o.val);
# 这里输出o.val而不是O.val
- 用apply或call或bind方法改变this指向
-
function tt(){
console.log(arguments.callee); # 永远是tt本身
console.log(this); # 都是下面定义的a
}
var a = '1';
tt.call(a, 1, 2);
tt.apply(a, [1, 2]);
var att = tt.bind(a);
att();
- 参数调用时,会扩展作用域,如
-
f(a.b)() # a挂到f的作用域
var f = function(c){}
- 作用域链(scope chain)
- 特点
- 每一段js代码有关联的作用域链
- 一个对象链表,定义这段代码的作用域
- 变量解析(variable resolution)时,从链第一个开始查找到最后一个 # 查找不存在时抛出引用错误(ReferenceError)
- 原理
- 定义一个函数时,实际上保存一个作用域链
- 调用该函数时,创建新对象放局部变量,添加到保存的作用域链
- 同时,创建一个新的、更长的”函数调用作用域链”
- 该函数每次调用外部函数时,嵌套函数重定义
- 代码作用域链分类
- 顶层代码
- 无嵌套函数体
- 嵌套函数体
- var定义的局部变量
- 顶级对象属性
- 函数调用作用域”链”
- 注意
- 函数创建时,它的作用域链中会填入全局对象
- 执行此函数时会创建一个称为“运行期上下文(execution context)”的内部对象
- 运行期上下文定义了函数执行时的环境
- 每个运行期上下文都有自己的作用域链
- 其作用域链初始化为当前运行函数的Scope所包含的对象。
- 函数中的值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中
- 它们共同组成了一个新的对象,叫“活动对象(activation object)”
- 该对象包含了函数的所有局部变量、命名参数、参数集合以及this
- 此对象会被推入作用域链的前端
- 运行期上下文被销毁,活动对象也随之销毁
- 在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。
- 该过程从作用域链头部,也就是从活动对象开始搜索
- 如果没找到继续搜索作用域链中的下一个对象
- 如果搜索完所有对象都未找到,则认为该标识符未定义
- 作用域链只会被 with 语句和 catch 语句影响。
- 优化代码:
- 因为全局变量总是存在于运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。
- 所以,在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量
- 一个好的经验法则是, 如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用
- 优化with
- with(o){}
- 使用with语句来避免多次书写document,看上去更高效,实际上产生了性能问题。
- 代码运行到with语句时,运行期上下文的作用域链临时被改变了
- 一个新的可变对象被创建,它包含了参数指定的对象的所有属性, 这个对象将被推入作用域链的头部
- 这意味着函数的所有局部变量现在处于第二个作用域链对象中,因此访问代价更高了。
- 优化try-catch
-
try{
doSomething();
}catch(ex){
alert(ex.message);
}
# 作用域链在此处改变。同理,catch语句使效率下降
try{
doSomething();
}catch(ex){
handleError(ex);
# 委托给处理器方法, 没有局部变量的访问,作用域链的临时改变就不会影响代码性能了。
}
# 优化后的代码,handleError方法是catch子句中唯一执行的代码。该函数接收异常对象作为参数,这样你可以更加灵活和统一的处理错误。