JavaScript
一、JS编程风格是函数式编程和面向对象编程的混合体
二、基本概念
js变量是松散的动态类型,每个变量仅仅是一个保存值的占位符,所以未初始化就是一个undefined,且可以随时切换类型
在变量声明时会被分为两步:变量的声明和赋值,变量声明命令通知解释引擎,要创建一个变量
- var a=1; ==> var a; a=1;
如果只是声明变量而没有赋值,则该变量的值是undefined,访问一个未声明的变量会导致抛出一个引用错误(ReferenceError)异常
可以使用 undefined 来判断一个变量是否已赋值(a===undefined)
变量提升hoisting
-
JS引擎工作方式是先解析代码获取所有被声明的变量语句,再按行运行,所以所有变量声明语句都被提升到代码头部
- console.log(a); var a=1; => undefine
null表示一个空对象指针,所以typeof null == object
转为false的值:空字符串、0、NaN、null(与之相对的是任何对象)、undefined
- Boolean({}) == true
相等和不相等:转换后再比较
全等和不全等:不转换就比较
对于var而言,区块{}不构成作用域
三、变量
声明
-
var
- 没有块作用域、声明提升
-
let
- 所声明的变量,只在let命令所在的代码块内有效
- 不存在变量提升(let、const)
- 暂时性死区
temporal dead zone
简称 TDZ
-
在代码块内,使用let命令声明变量之前,该变量都是不可用的
-
1)TDZ意味着typeof不再是一个百分之百安全的操作(此前即使变量不存在,也是返回undefined)
- typeof x; // ReferenceError let x;
-
2)函数参数也是声明
- function bar(x = y, y = 2) { return [x, y]; }
-
bar(); // 报错
- 3)声明前使用(赋值时先计算等号右边,再将计算结果赋值给左边变量)
- // 报错
let x = x; // ReferenceError: x is not defined
- 本质:只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
- let不允许在相同作用域内,重复声明同一个变量
- let实际上为 JavaScript 新增了块级作用域
- ES5 只有全局作用域和函数作用域,没有块级作用域。ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明
- ES6 明确允许在块级作用域之中声明函数,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用
- 块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要
- 但是,浏览器并不一定遵守约定,导致:
1)允许在块级作用域内声明函数。 2)函数声明类似于var,即会提升到全局作用域或函数作用域的头部。 3)函数声明还会提升到所在的块级作用域的头部。 - 考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句
- for循环的计数器,就很合适使用let命令。设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域
-
const
-
const声明一个只读的常量。一旦声明,常量的值就不能改变,所以,const一旦声明变量,就必须立即初始化
-
const命令声明的常量不提升 同样存在暂时性死区 只能在声明的位置后面使用 不可重复声明 只在声明的块中有效
-
本质:常量标识符指向的那个内存地址所保存的数据不得改动
- 原始类型的值就存放在该块内存区域,等同于常量
- 引用类型:标识符存放的是内存地址,即指向实际数据的指针,这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了
-
-
(ES6)如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错
-
ES6 一共有 6 种声明变量的方法:var、let、const、function、import、class
顶层对象
-
在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的
-
顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一
-
ES6 做出改变,规定
- 1)为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性
- 2)let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性
- 也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩
-
各种实现里面是不统一的
- 1)浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。 2)浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。 3)Node 里面,顶层对象是global,但其他环境都不支持。
-
ES2020 在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this
解构赋值
-
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)
- let [a, b, c] = [1, 2, 3];
-
数组
-
本质上,属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
-
解构不成功,变量的值就等于undefined
-
不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组
- let [x, y] = [1, 2, 3]; x // 1 y // 2
-
如果等号右边是不可遍历的结构,就会报错
-
事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值
-
解构赋值允许指定默认值
- let [x, y = 'b'] = ['a']; // x='a', y='b' let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
- ES6 内部使用严格相等运算符(===),判断一个位置是否有值。只有当一个数组成员严格等于undefined,默认值才会生效,null不全等undefined
- 如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值,所以能取到值时根本不会执行函数
- 默认值可以引用解构赋值的其他变量,但该变量必须已经声明(TDZ)
-
-
对象
-
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值
- let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb"
-
如果解构失败,变量的值等于undefined
-
可以很方便地将现有对象的方法,赋值到某个变量
- // 例二 const { log } = console; log('hello') // hello
-
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。前者是键是模式,后者是值是变量
- let { foo: baz } = { foo: 'aaa', bar: 'bbb' }; baz // "aaa" foo // error: foo is not defined
-
解构也可以用于嵌套结构的对象
-
对象的解构赋值可以取到继承的属性
-
对象的解构也可以指定默认值
- 默认值生效的条件是,对象的属性值严格等于undefined
-
只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题,放在一个圆括号里面,就可以正确执行
-
数组本质是特殊的对象,因此可以对数组进行对象属性的解构
- let arr = [1, 2, 3]; let {0 : first, [arr.length - 1] : last} = arr; first // 1 last // 3
-
-
基本类型
-
字符串
- 字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象
-
数值 布尔值
- 解构赋值时,如果等号右边是数值和布尔值,则会先转为对象
-
-
函数参数
-
函数的参数也可以使用解构赋值
-
函数参数的解构也可以使用默认值
- function move({x, y} = { x: 0, y: 0 }) { return [x, y]; }
-
move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, undefined] move({}); // [undefined, undefined] move(); // [0, 0] - 1)函数参数不为undefined,所以不使用函数参数默认值,x=3,y=8; 2)函数参数不为undefined,所以不使用函数参数默认值,x=3,y=undefined; 3)函数参数不为undefined,所以不使用函数参数默认值,x=undefined,y=undefined; 4)函数参数为undefined,所以使用函数参数默认值,x=0,y=0;
-
用途
-
交换变量的值
-
从函数返回多个值
- 函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便
-
函数参数的定义
- 解构赋值可以方便地将一组参数与变量名对应起来
-
提取 JSON 数据
-
解构赋值对提取 JSON 对象中的数据,尤其有用
- let jsonData = { id: 42, status: "OK", data: [867, 5309] };
-
-
let { id, status, data: number } = jsonData;
console.log(id, status, number); // 42, "OK", [867, 5309]
- 函数参数的默认值
- 避免了在函数体内部再写var foo = config.foo || 'default foo';这样的语句
- 遍历 Map 结构
- 部署了 Iterator 接口的对象,都可以用for...of循环遍历
- const map = new Map();
map.set('first', 'hello'); map.set('second', 'world');
for (let [key, value] of map) { console.log(key + " is " + value); } // first is hello // second is world
- 输入模块的指定方法
- 解构赋值使得输入语句非常清晰
- const { SourceMapConsumer, SourceNode } = require("source-map");
四、控制语句
if
- if(布尔值) 语句或语句块;
if...else
- if(布尔值){语句}else{语句};
if...else if...else
for (初始化表达式; 条件; 递增表达式)
语句 // 或者 for (初始化表达式; 条件; 递增表达式) { 语句 }
-
1)初始化表达式(initialize):确定循环变量的初始值,只在循环开始时执行一次。 2)条件表达式(test):每轮循环开始时,都要执行这个条件表达式,只有值为真,才继续进行循环。 3)递增表达式(increment):每轮循环的最后一个操作,通常用来递增循环变量。
-
for语句的三个部分(initialize、test、increment),可以省略任何一个,也可以全部省略。
-
for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域
- for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc
while (条件)
语句; // 或者 while (条件) 语句;
do
语句 while (条件); // 或者 do { 语句 } while (条件);
- 与while循环类似,唯一的区别就是先运行一次循环体,然后判断循环条件。
- 不管条件是否为真,do...while循环至少运行一次,这是这种结构最大的特点
for in
- 使用in操作符
- 用以枚举对象的所有属性,属性没有顺序,所以循环顺序不可预测
switch case
-
switch使用全等,传入true,会依次判断case
-
switch(表达式){case 表达式:....}
-
case语句块中的break不能少
-
建议switch...case结构可以用对象结构代替
- function doAction(action) { switch (action) { case 'hack': return 'hack'; case 'slash': return 'slash'; case 'run': return 'run'; default: throw new Error('Invalid action.'); } }
- function doAction(action) { var actions = { 'hack': function () { return 'hack'; }, 'slash': function () { return 'slash'; }, 'run': function () { return 'run'; } };
if (typeof actions[action] !== 'function') { throw new Error('Invalid action.'); }
return actionsaction; }
三元运算符
- (条件) ? 表达式1 : 表达式2
break语句和continue语句都具有跳转作用,可以让代码不按既有的顺序执行
- break语句用于跳出代码块或循环。
- continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环
label:
语句
- 语句的前面有标签(label),相当于定位符,用于跳转到程序的任意位置。常与break语句和continue语句配合使用,跳出特定的循环或代码块
五、数据类型
JavaScript 语言的每一个值,都属于某一种数据类型
原始类型
基础类型
-
基本类型:简单的数据段,undefined、null、boolean、number、string,按值访问,可以操作保存在变量中实际的值,栈内存
-
undefined
- undefined是一个表示"此处无定义"的原始值,转为数值时为NaN
- // 1)变量声明了,但没有赋值 var i; i // undefined
// 2)调用函数时,应该提供的参数没有提供,该参数等于 undefined function f(x) { return x; } f() // undefined
// 3)对象没有赋值的属性 var o = new Object(); o.p // undefined
// 4)函数没有返回值时,默认返回 undefined function f() {} f() // undefined
-
null
- null是一个表示“空”的对象,转为数值时为0
-
boolean
- 下列运算符会返回布尔值: 1)前置逻辑运算符: ! (Not) 2)相等运算符:===,!==,==,!= 3)比较运算符:>,>=,<,<=
- 如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值,undefined、null、false、0、NaN、""或''(空字符串)六个值会转为false。注意,空数组([])和空对象({})对应的布尔值,都是true
-
number
-
JS内部,所有数字都是以64位浮点数形式储存,即使整数也是如此,底层根本没有整数,所有数字都是小数,浮点数不精确,涉及小数的计算和比较需要注意
- 1 === 1.0 // true
-
数值精度
- (-1)^符号位 * 1.xx...xx * 2^指数部分
-
数值范围
-
数值进制
-
使用字面量(literal)直接表示一个数值时,JS对整数提供四种进制的表示方法:十进制、十六进制、八进制、二进制: 1)十进制(Decimal):没有前导0的数值。 2)八进制(Octal):有前缀0o或0O的数值,且只用到0-7的八个阿拉伯数字的数值。 3)十六进制(Hex):有前缀0x或0X的数值。 4)二进制(Binary):有前缀0b或0B的数值。
- 前导0表示八进制,处理时很容易造成混乱。ES5 的严格模式和 ES6,已经废除了这种表示法
-
默认情况下,JS内部会自动将八进制、十六进制、二进制转为十进制
-
-
特殊数值
-
正零和负零
- 除当做分母外不同(得到正负Infinity),其余时刻都等于正常0
-
NaN(Not a Number)
-
表示一个本要返回数值的操作数未返回数值的情况,避免抛出错误。出现在将字符串解析成数字、数学函数的运算结果出错的场合
- 5 - 'x' // NaN Math.acos(2) // NaN Math.log(-1) // NaN Math.sqrt(-1) // NaN 0 / 0 // NaN
-
注意,NaN不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于number,使用typeof运算符可以看得很清楚。
- typeof NaN // 'number'
-
NaN与任何数(包括它自己)的运算,得到的都是NaN,NaN不等于任何值,包括它本身
-
涉及NaN的操作都返回NaN
-
NaN不与任何值相等,包括NaN,只能使用isNaN函数判断一个值是否为NaN
- 对象的isNaN:先valueOf,再toString
-
-
Infinity
- 表示“无穷”,表示两种场景。一种是一个正的数值太大,或一个负的数值太小,无法表示;另一种是非0数值除以0,得到Infinity
-
-
全局方法
-
parseInt()
-
用于将字符串转为整数;
- parseInt('123') // 123
-
1)如果字符串头部有空格,空格会被自动去除;
- parseInt(' 81') // 81
-
2)如果parseInt的参数不是字符串,则会先转为字符串再转换;
- parseInt(1.23) // 1 // 等同于 parseInt('1.23') // 1
-
-
-
parseInt(0x11, 36) // 43 parseInt(0x11, 2) // 1 // 等同于 parseInt(String(0x11), 36) parseInt(String(0x11), 2) // 等同于 parseInt('17', 36) parseInt('17', 2) 0x11先默认转为十进制17,再转为字符串
- 3)字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分;(Number函数是有非数字就NaN)
- parseInt('8a') // 8
parseInt('12**') // 12 parseInt('12.34') // 12 parseInt('15e2') // 15 parseInt('15px') // 15
- 4)如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN
- parseInt('abc') // NaN
parseInt('.3') // NaN parseInt('') // NaN parseInt('+') // NaN parseInt('+1') // 1
- 返回值只有两种可能,要么是一个十进制整数,要么是NaN
- 5)如果字符串以0x或0X开头,parseInt会将其按照十六进制数解析;如果字符串以0开头,将其按照10进制解析
- 进制转换:接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数
- parseInt('1000', 10) // 1000
parseInt('1000', 2) // 8 parseInt('1000', 6) // 216 parseInt('1000', 8) // 512
- parseFloat()
- 用于将一个字符串转为浮点数
- 如果字符串包含不能转为浮点数的字符,则不再进行往后转换,返回已经转好的部分
- 自动过滤字符串前导的空格
- 如果参数不是字符串,或者字符串的第一个字符不能转化为浮点数,则返回NaN
- parseFloat([]) // NaN
parseFloat('FF2') // NaN parseFloat('') // NaN
- isNaN()
- 用来判断一个值是否为NaN
- isNaN(NaN) // true
isNaN(123) // false
- isNaN只对数值有效,如果传入其他值,会被先转成数值
- isNaN('Hello') // true
// 相当于 isNaN(Number('Hello')) // true
- 但是,对于空数组和只有一个数值成员的数组,isNaN返回false
- isFinite()
- 返回一个布尔值,表示某个值是否为正常的数值
-
string
-
字符串就是零个或多个排在一起的字符,放在单引号或双引号之中
-
反斜杠(\)在字符串内有特殊含义,用来表示一些特殊字符,所以又称为转义符
-
字符串可以被视为字符数组
- 可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)
- 如果方括号中的数字超过字符串的长度,或者方括号中根本不是数字,则返回undefined
- 实际上,无法改变字符串之中的单个字符
-
length属性返回字符串的长度,该属性也是无法改变的
-
每个字符在 JavaScript 内部都是以16位(即2个字节)的 UTF-16 格式储存。也就是说,JS的单位字符长度固定为16位长度,即2个字节
-
Base64 转码
-
文本里面包含一些不可打印的符号,比如 ASCII 码0到31的符号都无法打印出来,这时可以使用 Base64 编码,将它们转成可以打印的字符。另一个场景是,有时需要以文本格式传递二进制数据,那么也可以使用 Base64 编码
-
Base64 就是一种编码方法,可以将任意值转成 0~9、A~Z、a-z、+和/这64个字符组成的可打印字符。使用它的主要目的,不是为了加密,而是为了不出现特殊字符,简化程序的处理
-
原生方法
-
btoa():任意值转为 Base64 编码(原生)
-
atob():Base64 编码转为原来的值(原生)
-
注意,这两个方法不适合非 ASCII 码的字符,会报错。要将非 ASCII 码字符转为 Base64 编码,必须中间插入一个转码环节,再使用这两个方法
- function b64Encode(str) { return btoa(encodeURIComponent(str)); }
-
-
-
function b64Decode(str) { return decodeURIComponent(atob(str)); }
b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE" b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
合成类型
引用类型
-
引用类型:object、array、function,多个值构成的对象,js不允许直接访问内存地址,只能操作对象的引用,按引用访问,堆内存
-
object
-
简单说,对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合
- var obj = { foo: 'Hello', bar: 'World' };
-
对象的所有键名都是字符串(数值会被自动转为字符串,其他类型会报错)
-
如果键名是数值,会被自动转为字符串,注意js中其他进制会被自动转为十进制
- var obj = { 1: 'a', 3.2: 'b', 1e2: true, 1e-2: true, .234: true, 0xFF: true };
-
obj // Object { // 1: "a", // 3.2: "b", // 100: true, // 0.01: true, // 0.234: true, // 255: true // }
- 对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用
- 属性可以动态创建,不必在对象声明时就指定
- 1)不同的变量名指向同一个对象,则都是这个对象的引用,即指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。
2)两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝 - 无法确定是对象还是代码块,一律解释为代码块。如果要解释为对象,最好在大括号前加上圆括号。圆括号的里面,只能是表达式,所以确保大括号只能解释为对象
- ({ foo: 123 }) // 正确
({ console.log(123) }) // 报错
- 属性操作
- 读取
- 1)点运算符
2)方括号运算符
- var obj = {
p: 'Hello World' }; obj.p // "Hello World" obj['p'] // "Hello World"
- 方括号运算符内部使用表达式。
所以,键名必须放在引号里面,否则会被当作变量处理。 数字键可以不加引号,会自动转成字符串
- obj['hello' + ' world']
obj[3 + 3]
- 数值键名不能使用点运算符(会被当成小数点),只能使用方括号运算符 (数组也是对象)
- var obj = {
123: 'hello world' }; obj.123 // 报错 obj[123] // "hello world"
- 赋值
- 点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值
- var obj = {};
obj.foo = 'Hello'; obj['bar'] = 'World';
- JS允许属性的“后绑定”,也就是说,你可以在任意时刻新增属性
- 查看
- 查看一个对象本身的所有属性,可以使用Object.keys方法
- var obj = {
key1: 1, key2: 2 }; Object.keys(obj); // ['key1', 'key2']
- 删除
- delete命令用于删除对象的属性,删除成功后返回true
- var obj = { p: 1 };
Object.keys(obj) // ["p"] delete obj.p // true obj.p // undefined Object.keys(obj) // []
- 删除一个不存在的属性,delete不报错,而且返回true
- 只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除
- var obj = Object.defineProperty({}, 'p', {
value: 123, configurable: false });
obj.p // 123 delete obj.p // false
- delete命令只能删除对象本身的属性,无法删除继承的属性
- 是否存在
- in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值)
- var obj = { p: 1 };
'p' in obj // true 'toString' in obj // true
- in运算符不能识别哪些属性是对象自身的,哪些属性是继承的
- 使用对象的hasOwnProperty方法判断是否为对象自身的属性
- var obj = {};
if ('toString' in obj) { console.log(obj.hasOwnProperty('toString')) // false }
- 遍历
- for...in循环用来遍历一个对象的全部属性:
1)遍历对象所有可遍历(enumerable)属性,跳过不可遍历的属性 2)遍历对象自身的属性和继承的属性 - 遍历对象自身的属性,使用for...in的时候,应该结合hasOwnProperty方法,在循环内部判断某个属性是否为对象自身的属性
-
function
-
函数是一段可以反复调用的代码块,函数名是指针,所以没有重载
-
声明
-
1)function命令声明的代码区块,就是一个函数。函数提升,整个函数会像变量声明一样,被提升到代码头部。但如果采用赋值语句定义函数,JS会报错。
- f(); var f = function (){}; // TypeError: undefined is not a function ==> var f; f(); f = function () {};
-
2)函数表达式:将一个匿名函数赋值给变量。这个匿名函数又称函数表达式(Function Expression),因为赋值语句的等号右侧只能放表达式。function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效
- var print = function(s) { console.log(s); };
-
如果同一个函数被多次声明,后面的声明就会覆盖前面的声明,由于函数名的提升,前一次声明在任何时候都是无效的
-
-
圆括号运算符:函数名后面紧跟一对圆括号,就会调用/执行这个函数
-
return语句,表示返回。return语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined
-
一等公民:JS语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。函数只是一个可以执行的值,此外并无特殊之处。
-
属性/方法
-
name属性返回函数的名字
- 通过变量赋值定义的函数,那么name属性返回变量名;
- 如果变量的值是一个具名函数,那么name属性返回function关键字之后的那个函数名
-
length属性返回函数预期传入的参数个数,即函数定义之中的参数个数,不管调用时输入了多少个参数,函数声明时确定
-
toString方法返回一个字符串,内容是函数的源码
- 原生函数toString()方法返回function (){[native code]}
-
-
函数作用域
-
作用域(scope)指的是变量存在的范围
-
对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量
-
函数作用域内部也会产生“变量提升”现象
-
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。函数体内部声明的函数,作用域绑定函数体内部
- var a = 1; var x = function () { console.log(a); };
-
-
function f() { var a = 2; x(); }
f() // 1
- 执行环境、
变量对象、 作用域链
- 执行环境(excutiosn context)定义变量或函数有权访问的其他数据,每个函数都有自己的执行环境
- 变量对象(variable object)每个执行环境都有一个与之关联的变量对象,保存执行环境中的变量和函数
- 当执行流进入一个函数的时候,函数的执行环境就会被压进一个环境栈,函数执行结束,栈弹出其环境,将控制权交给之前的执行环境
- 作用域链(scope chain)保证对执行环境有权访问的变量和函数的有序访问,其前端始终是当前执行环境的变量对象,下一个变量对象来自包含环境(外部),直到全局执行环境(作用域链的最后一个对象)
- 标识符的解析是沿着作用域链一级级的解析,从前端开始,直到全局执行环境
- 参数
- 函数参数不是必需的,JS允许省略参数,省略的参数值变为undefined,函数的length属性与实际传入的参数个数无关,只反映函数预期传入的参数个数
- 没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined
- 传递方式
- 原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value),函数体内修改参数值,不会影响到函数外部
- 复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。传入函数的原始值的地址,在函数内部修改参数,会影响到原始值
- 如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。因为这时是重新赋值,函数参数在函数括号中时声明
- 如果有同名的参数,则取最后出现的那个值
- arguments
- JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数
- arguments对象包含了函数运行时的所有参数
- 这个对象只有在函数体内部,才可以使用
- 正常模式下,arguments对象可以在运行时修改;严格模式下,arguments对象与函数参数不具有联动关系
- length属性,可以判断函数调用时到底带几个参数,因为arguments对象在运行时才会创建,函数运行时确定
- 像数组,但它是一个对象,可以将arguments转为真正的数组
- var args = Array.prototype.slice.call(arguments);
// 或者 var args = []; for (var i = 0; i < arguments.length; i++) { args.push(arguments[i]); }
- callee属性,返回它所对应的原函数,严格模式下禁用
- 闭包(Closure)
- 能够读取其他函数内部变量的函数,简单理解成“定义在一个函数内部的函数”
- 最大的特点,就是它可以“记住”诞生的环境;本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁
- 注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大
- 立即调用的函数表达式(IIFE)
Immediately-Invoked Function Expression
- JavaScript 引擎规定,如果function关键字出现在行首,一律解释成语句
-
array
-
按次序排列的一组任何类型数据,每个值的位置都有编号(从0开始),整个数组用方括号表示
-
本质上,数组属于一种特殊的对象(特殊在键都是数字,而数字会自动转为字符串)。typeof运算符会返回数组的类型是object
-
length属性
- 返回数组的成员数量,是一个动态的值,等于键名中的最大整数数字键加上1
- 数组的数字键不需要连续,length属性的值总是比最大的那个整数键大1
- 数组是一种动态的数据结构,可以随时增减数组的成员
- length属性是可写的。如果设置一个小于当前成员个数的值,数组成员会自动减少到length设置的值
- 清空数组的一个有效方法,就是将length属性设为0。
- 当length属性设为大于数组个数时,读取新增的位置都会返回undefined。(相当于声明了,但未赋值)
-
可以为数组添加属性,但是这不影响length属性的值
-
in 运算符
- 检查某个键名是否存在的运算符in,适用于对象,也适用于数组,in操作符针对对象的键/属性
-
数组遍历
-
for...in循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象,但是会遍历到数组中后添加的属性,所以不推荐
- var a = [1, 2, 3]; a.foo = true;
-
-
for (var key in a) { console.log(key); } // 0 // 1 // 2 // foo
- 使用for循环或while循环
- forEach方法,也可以用来遍历数组
- var colors = ['red', 'green', 'blue'];
colors.forEach(function (color) { console.log(color); }); // red // green // blue
- 空位
- 当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位(hole)
- 空位不影响length属性
- 是可以读取的,返回undefined
- 使用delete命令删除一个数组成员,会形成空位,并且不会影响length属性
- var a = [1, 2, 3];
delete a[1];
a[1] // undefined a.length // 3
- 某个位置是空位,与某个位置是undefined,是不一样的
- 如果是空位,使用数组的forEach方法、for...in结构、以及Object.keys方法进行遍历,空位都会被跳过。
- 如果某个位置是undefined,遍历的时候就不会被跳过。
- 类数组对象
array-like object
- 如果一个对象的所有键名都是正整数或零,并且有length属性,那么这个对象就很像数组
- “类似数组的对象”并不是数组,因为它们不具备数组特有的方法
- 根本特征,就是具有length属性。只要有length属性,就可以认为这个对象类似于数组,但是这个length不是动态的
- 典型的“类似数组的对象”是函数的arguments对象,以及大多数 DOM 元素集,还有字符串
- 转变成真正的数组
- var arr = Array.prototype.slice.call(arrayLike);
- 扩展运算符...
- rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列
- 只有函数调用时,扩展运算符才可以放在圆括号中
- 扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了
- // ES5 的写法
function f(x, y, z) { // ... } var args = [0, 1, 2]; f.apply(null, args);
// ES6的写法 function f(x, y, z) { // ... } let args = [0, 1, 2]; f(...args); - // ES5 的写法 Math.max.apply(null, [14, 3, 77])
// ES6 的写法 Math.max(...[14, 3, 77])
// 等同于 Math.max(14, 3, 77);
- 复制数组
- 数组是复合数据类型/引用类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组
- const a1 = [1, 2];
// 写法一 const a2 = [...a1]; // 写法二 const [...a2] = a1;
- 合并数组
- const arr1 = ['a', 'b'];
const arr2 = ['c']; const arr3 = ['d', 'e'];
// ES5 的合并数组 arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组 [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]
- 与解构赋值结合
- 将字符串转为真正的数组
类型判断
-
typeof运算符
- typeof 123 // "number" typeof '123' // "string" typeof false // "boolean" function f() {} typeof f // "function" typeof undefined // "undefined" typeof window // "object" typeof {} // "object" typeof [] // "object" typeof null // "object"
- 可以用来检查一个没有声明的变量,而不报错,在判断语句中使用
-
instanceof运算符
-
Object.prototype.toString方法
- const TYPE = {};
for (let i = 0, type; type = ["Boolean", "String", "Number", "Array", "Function", "Object"][i++];) {
TYPE[is${type}] = obj => Object.prototype.toString.call(obj) === [object ${type}];
}
TYPE.isNumber(1) // true
- 确定基本类型使用typeof,确定引用类型使用instanceof(不精确)
类型转换
-
JavaScript 是一种动态类型语言,变量没有类型限制,可以随时赋予任意值
-
变量的数据类型是不确定的,但是各种运算符对数据类型是有要求的。如果运算符发现,运算子的类型与预期不符,就会自动转换类型
-
强制转换
-
Number()
-
将任意类型的值转化成数值(转换成原始类型)
-
原始类型
- 1)数值:转换后还是原来的值 Number(324) // 324
-
-
2)字符串:如果可以被解析为数值,则转换为相应的数值 Number('324') // 324
3)字符串:如果不可以被解析为数值,返回 NaN Number('324abc') // NaN
4)空字符串转为0 Number('') // 0
5)布尔值:true 转成 1,false 转成 0 Number(true) // 1 Number(false) // 0
6)undefined:转成 NaN Number(undefined) // NaN
7)null:转成0 Number(null) // 0 - Number函数将字符串转为数值,要比parseInt函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaN。 - parseInt逐个解析字符,而Number函数整体转换字符串的类型 - parseInt和Number函数都会自动过滤一个字符串前导和后缀的空格
- 引用类型
- 简单的规则是,Number方法的参数是对象时,将返回NaN,除非是包含单个数值的数组
- 第一步,调用对象自身的valueOf方法。如果返回原始类型的值,则直接对该值使用Number函数,不再进行后续步骤。
第二步,如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法。如果toString方法返回原始类型的值,则对该值使用Number函数,不再进行后续步骤。
第三步,如果toString方法返回的是对象,就报错。
valueOf() ==> toString()
- String()
- 将任意类型的值转化成字符串
- 原始类型
- 1)数值:转为相应的字符串。
2)字符串:转换后还是原来的值。 3)布尔值:true转为字符串"true",false转为字符串"false"。 4)undefined:转为字符串"undefined"。 5)null:转为字符串"null"。
- 引用类型
- 参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式
- String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"
- 1)先调用对象自身的toString方法。如果返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
2)如果toString方法返回的是对象,再调用原对象的valueOf方法。如果valueOf方法返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
3)如果valueOf方法返回的是对象,就报错。
toString() ==> valueOf()
- Boolean()
- 将任意类型的值转为布尔值
- 除了以下五个值的转换结果为false,其他的值全部为true。
undefined null 0(包含-0和+0) NaN ''(空字符串) - 注意,所有对象(包括空对象)的转换结果都是true,甚至连false对应的布尔对象new Boolean(false)也是true(所有对象的布尔值都是true)
-
自动转换
-
预期什么类型的值,就调用该类型的转换函数(强制转换的函数)
-
场景: 1)不同类型的数据互相运算 2)对非布尔值类型的数据求布尔值 3)对非数值类型的值使用一元运算符(即+和-)
-
自动转为布尔值
- 除了以下五个值,其他都是自动转为true。 undefined null +0或-0 NaN ''(空字符串)
-
自动转换字符串
-
遇到预期为字符串的地方,就会将非字符串的值自动转为字符串。具体规则是,先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串
-
字符串的自动转换,主要发生在字符串的加法运算时。当一个值为字符串,另一个值为非字符串,则后者转为字符串。
- '5' + 1 // '51' '5' + true // "5true" '5' + false // "5false" '5' + {} // "5[object Object]" '5' + [] // "5" '5' + function (){} // "5function (){}" '5' + undefined // "5undefined" '5' + null // "5null"
-
-
自动转为数值
- 自动调用Number函数
- 除了加法运算符(+)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值
- null转为数值时为0,而undefined转为数值时为NaN
-
六、运算符
算术
-
加法
-
JavaScript 允许非数值的相加,加法运算符存在重载,可能执行两种运算
-
布尔值都会自动转成数值,然后再相加
- true + true // 2 1 + true // 2
-
两个字符串相加,这时加法运算符会变成连接运算符,返回一个新的字符串,将两个原字符串连接在一起
- 'a' + 'bc' // "abc"
-
如果一个运算子是字符串,另一个运算子是非字符串,这时非字符串会转成字符串,再连接在一起
- 1 + 'a' // "1a" false + 'a' // "falsea"
-
其他算术运算符(比如减法、除法和乘法)都不会发生重载。它们的规则是:所有运算子一律转为数值,再进行相应的数学运算,注意NaN
-
对象的相加:如果运算子是对象,必须先转成原始类型的值,然后再相加
-
对象转成原始类型的值: 1)首先,自动调用对象的valueOf方法 2)再自动调用对象的toString方法,将其转为字符串
-
-
求余
-
返回前一个运算子被后一个运算子除,所得的余数
-
运算结果的正负号由第一个运算子的正负号决定,为了得到负数的正确余数值,可以先使用绝对值函数
- -1 % 2 // -1 1 % -2 // 1
-
-
指数
- 指数运算符(**)完成指数运算,前一个运算子是底数,后一个运算子是指数
- 指数运算符是右结合,而不是左结合
比较
-
非相等运算符
-
字符串按照字典顺序进行比较,比较字符的 Unicode 码点
- 'cat' > 'Cat' // true'
-
原始类型值
-
两个运算子都是原始类型的值,则是先转成数值再比较
- 5 > '4' // true // 等同于 5 > Number('4') // 即 5 > 4
-
任何值(包括NaN本身)与NaN比较,返回的都是false
-
-
对象
- 如果运算子是对象,会转为原始类型的值,再进行比较
- 先调用valueOf方法;如果返回的还是对象,再接着调用toString方法
-
-
相等运算符
-
相等运算符(==)比较两个值是否相等
-
原始类型的值会转换成数值再进行比较
- 'true' == true // false // 等同于 Number('true') === Number(true) // 等同于 NaN === 1
-
对象(这里指广义的对象,包括数组和函数)与原始类型的值比较时,对象转换成原始类型的值,再进行比较
-
undefined和null与其他类型的值比较时,结果都为false,它们互相比较时结果为true
-
相等运算符隐藏的类型转换,会带来一些违反直觉的结果
- 0 == '' // true 0 == '0' // true
-
-
严格相等运算符 全等运算符
-
严格相等运算符(===)比较它们是否为“同一个值”
-
同一类型的原始类型的值(数值、字符串、布尔值)比较时,值相同就返回true,值不同就返回false
-
两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址
- {} === {} // false [] === [] // false (function () {} === function () {}) // false
- 空对象、空数组、空函数的值,都存放在不同的内存地址,结果当然是false
-
对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值
-
undefined和null与自身严格相等
- undefined === undefined // true null === null // true
-
var v1; var v2; v1 === v2 // true
布尔
-
!
-
非布尔值,取反运算符会将其转为布尔值
- 以下六个值取反后为true,其他值都为false: undefined null false 0 NaN 空字符串('')
-
对一个值连续做两次取反运算,等于将其转为对应的布尔值,与Boolean函数的作用相同
- !!x // 等同于 Boolean(x)
-
-
&&
-
用于多个表达式的求值
-
运算规则是:如果第一个运算子的布尔值为true,则返回第二个运算子的值(注意是值,不是布尔值);如果第一个运算子的布尔值为false,则直接返回第一个运算子的值,且不再对第二个运算子求值
- if (i) { doSomething(); }
-
// 等价于
i && doSomething();
- 跳过第二个运算子的机制,被称为“短路”
- 多个连用时返回第一个布尔值为false的表达式的值。如果所有表达式的布尔值都为true,则返回最后一个表达式的值
- 因为与表达式由第一个false决定
-
||
-
用于多个表达式的求值
-
运算规则是:如果第一个运算子的布尔值为true,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为false,则返回第二个运算子的值
-
跳过第二个运算子的机制,被称为“短路”
-
多个连用时返回第一个布尔值为true的表达式的值。如果所有表达式都为false,则返回最后一个表达式的值
- 或表达式有第一个true决定
-
-
?:
- 三元条件运算符由问号(?)和冒号(:)组成,分隔三个表达式
二进制位
-
二进制位运算符用于直接对二进制位进行计算
-
直接处理每一个比特位(bit),非常底层的运算,好处是速度极快
-
位运算符只对整数起作用,如果一个运算子不是整数,会自动转为整数后再执行
-
在 JavaScript 内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数
- i = i | 0; //将i(不管是整数或小数)转为32位整数
-
|
- 或运算符(|)逐位比较两个运算子,两个二进制位之中只要有一个为1,就返回1,否则返回0
-
&
- 与运算符(&)逐位比较两个运算子,两个二进制位之中只要有一个位为0,就返回0,否则返回1
-
~
- 否运算符(~)将每个二进制位都变为相反值(0变为1,1变为0)
- 对一个整数连续两次二进制否运算,得到它自身;对一个小数连续进行两次二进制否运算,能达到取整效果(取整方法中最快的一种)
- 对其他类型进行二进制否运算,JavaScript 引擎会先调用Number函数,将字符串转为数值
-
^
-
异或运算(^)在两个二进制位不同时返回1,相同时返回0(相异则或)
-
连续对两个数a和b进行三次异或运算,a^=b; b^=a; a^=b;,可以互换它们的值(最快方法)
-
异或运算也可以用来取整
- 12.9 ^ 0 // 12
-
-
<<
- 左移运算符(<<)表示将一个数的二进制值向左移动指定的位数,尾部补0,即乘以2的指定次方
- 向左移动的时候,最高位的符号位是一起移动的
- 如果左移0位,就相当于将该数值转为32位整数,等同于取整,对于正数和负数都有效
-
- 右移运算符(>>)表示将一个数的二进制值向右移动指定的位数。如果是正数,头部全部补0;如果是负数,头部全部补1。右移运算符基本上相当于除以2的指定次方(最高位即符号位参与移动)。
-
- 头部补零的右移运算符(>>>)与右移运算符(>>)只有一个差别,就是一个数的二进制形式向右移动时,头部一律补零,而不考虑符号位
- 总是得到正值
-
开关作用
逗号运算符
-
逗号运算符用于对两个表达式求值,并返回后一个表达式的值
-
一个用途是,在返回一个值之前,进行一些辅助操作
- var value = (console.log('Hi!'), true); // Hi!
七、控制台
console
console对象是 JavaScript 的原生对象
用途
- 调试程序,显示网页代码运行时的错误信息。
- 提供了一个命令行接口,用来与网页代码互动
静态方法
-
log接受一个或多个参数,将它们连接起来输出
- console.log('Hello World') // Hello World console.log('a', 'b', 'c') // a b c
-
info是console.log方法的别名,用法完全一样。只不过console.info方法会在输出信息的前面,加上一个蓝色图标
-
debug与log类似,在控制台输出调试信息。默认情况下,debug输出的信息不会显示,只有在打开显示级别在verbose的情况下,才会显示
-
console对象的所有方法,都可以被覆盖
- ['log', 'info', 'warn', 'error'].forEach(function(method) { console[method] = console[method].bind( console, new Date().toISOString() ); });
console.log("出错了!"); // 2014-05-18T09:00.000Z 出错了!
-
warn和error也是在控制台输出信息 warn输出信息时,在最前面加一个黄色三角,表示警告 error输出信息时,在最前面加一个红色的叉,表示出错 同时,还会高亮显示输出文字和错误发生的堆栈
-
table方法可以将复合类型转为表格显示
-
count方法用于计数,输出它被调用了多少次
-
dir方法用来对一个对象进行检查(inspect),并以易于阅读和打印的格式显示
- 对于输出 DOM 对象非常有用,因为会显示 DOM 对象的所有属性
- Node 环境之中,还可以指定以代码高亮的形式输出
-
dirxml方法主要用于以目录树的形式,显示 DOM 节点
-
assert方法主要用于程序运行过程中,进行条件判断,如果不满足条件,就显示一个错误,但不会中断程序执行
- 接受两个参数,第一个参数是表达式,第二个参数是字符串。只有当第一个参数为false,才会提示有错误,在控制台输出第二个参数,否则不会有任何结果
-
time、timeEnd方法用于计时,可以算出一个操作所花费的准确时间
- time方法表示计时开始,timeEnd方法表示计时结束。它们的参数是计时器的名称。
-
trace方法显示当前执行的代码在堆栈中的调用路径
-
clear方法用于清除当前控制台的所有输出,将光标回置到第一行
debugger语句主要用于除错,作用是设置断点
- Chrome 浏览器中,当代码运行到debugger语句时,就会暂停运行,自动打开脚本源码界面
八、标准库
Object对象
-
Object对象的原生方法分成两类:Object本身的方法与Object的实例方法
- 访问属性或方法:点运算符、方括号运算符(其中传入字符串,即可以使用变量)
-
Object本身是一个函数,可以当作工具方法使用,将任意值转为对象。这个方法常用于保证某个值一定是对象。
-
如果参数为空(或者为undefined和null),Object()返回一个空对象
- var obj = Object(); // 等同于 var obj = Object(undefined); var obj = Object(null);
-
obj instanceof Object // true
- 如果参数是原始类型的值,Object方法将其转为对应的包装对象的实例
- 如果Object方法的参数是一个对象,它总是返回该对象,即不用转换
-
Object不仅可以当作工具函数使用,还可以当作构造函数使用,即前面可以使用new命令
-
首要用途,是直接通过它来生成新对象
-
注意,通过var obj = new Object()的写法生成新对象,与字面量的写法var obj = {}是等价的。或者说,后者只是前者的一种简便写法
-
可以接受一个参数,如果该参数是一个对象,则直接返回这个对象;如果是一个原始类型的值,则返回该值对应的包装对象
- var o1 = {a: 1}; var o2 = new Object(o1); o1 === o2 // true
-
-
静态方法
-
所谓“静态方法”,是指部署在Object对象自身的方法
-
keys方法的参数是一个对象,返回一个数组。该数组的成员都是该对象自身的(而不是继承的)所有属性名,只返回可枚举的属性
-
getOwnPropertyNames方法与Object.keys类似,也是接受一个对象作为参数,返回一个数组,包含了该对象自身的所有属性名,还返回不可枚举的属性名
-
getOwnPropertyDescriptor():获取某个属性的描述对象
-
defineProperty():通过描述对象,定义某个属性
-
defineProperties():通过描述对象,定义多个属性
-
Object.preventExtensions():防止对象扩展。
- Object.isExtensible():判断对象是否可扩展。
-
Object.seal():禁止对象配置。
- Object.isSealed():判断一个对象是否可配置。
-
Object.freeze():冻结一个对象。
- Object.isFrozen():判断一个对象是否被冻结。
-
Object.create():该方法可以指定原型对象和属性,返回一个新的对象。
-
Object.getPrototypeOf():获取对象的Prototype对象。
-
-
实例方法
-
定义在Object.prototype对象,称为实例方法,所有Object的实例对象都继承了这些方法
-
Object.prototype.valueOf():返回当前对象对应的值。
- 默认情况下返回对象本身
- 自动类型转换时会默认调用这个方法
-
Object.prototype.toString():返回当前对象对应的字符串形式。
-
返回一个对象的字符串形式,默认情况下返回类型字符串
-
由于实例对象可能会自定义toString方法,覆盖掉Object.prototype.toString方法,所以为了得到类型字符串,最好直接使用Object.prototype.toString方法。通过函数的call方法,可以在任意值上调用这个方法,帮助我们判断这个值的类型
- 数值:返回[object Number]。
- 字符串:返回[object String]。
- 布尔值:返回[object Boolean]。
- undefined:返回[object Undefined]。
- null:返回[object Null]。
- 数组:返回[object Array]。
- arguments 对象:返回[object Arguments]。
- 函数:返回[object Function]。
- Error 对象:返回[object Error]。
- Date 对象:返回[object Date]。
- RegExp 对象:返回[object RegExp]。
- 其他对象:返回[object Object]
-
-
Object.prototype.toLocaleString():返回当前对象对应的本地字符串形式。
-
Object.prototype.hasOwnProperty():判断某个属性是否为当前对象自身的属性,还是继承自原型
-
Object.prototype.isPrototypeOf():判断当前对象是否为另一个对象的原型。
-
Object.prototype.propertyIsEnumerable():判断某个属性是否可枚举。
-
属性描述符
-
JavaScript 提供了一个内部数据结构,用来描述对象的属性,控制它的行为。这个内部数据结构称为“属性描述对象”(attributes object)。每个属性都有自己对应的属性描述对象,保存该属性的一些元信息
-
元属性
-
value
- 是该属性的属性值
-
writable
- 是一个布尔值,表示属性值(value)是否可改变(即是否可写),默认为true
- 注意,正常模式下,对writable为false的属性赋值不会报错,只会默默失败。但是,严格模式下会报错,即使对a属性重新赋予一个同样的值
- 如果原型对象的某个属性的writable为false,那么子对象将无法自定义这个属性,但是,通过覆盖属性描述对象,绕过这个限制,原型链会被完全忽视
-
enumerable
- 是一个布尔值,表示该属性是否可遍历,默认为true
- 如果一个属性的enumerable为false,下面三个操作不会取到该属性。 for..in循环 Object.keys方法 JSON.stringify方法
- 如果需要获取对象自身的所有属性,不管是否可遍历,可以使用Object.getOwnPropertyNames方法
-
configurable
- 是一个布尔值,表示可配置性,默认为true
- configurable为false时,value、writable、enumerable和configurable都不能被修改了
- 注意,writable只有在false改为true会报错,true改为false是允许的
- 至于value,只要writable和configurable有一个为true,就允许改动
- 可配置性决定了目标属性是否可以被删除(delete)
-
get
- 是一个函数,表示该属性的取值函数(getter),默认为undefined
- 取值函数get不能接受参数
-
set
- 是一个函数,表示该属性的存值函数(setter),默认为undefined
- 存值函数set只能接受一个参数(即属性的值)
-
-
Object.getOwnPropertyDescriptor()方法
- 可以获取属性描述对象
- 只能用于对象自身的属性,不能用于继承的属性
- 第一个参数是目标对象,
- 第二个参数是一个字符串,对应目标对象的某个属性名
-
Object.getOwnPropertyNames方法
- 返回一个数组,成员是参数对象自身的全部属性的属性名,不管该属性是否可遍历
- 跟Object.keys的行为不同,Object.keys只返回对象自身的可遍历属性的全部属性名
- 也返回继承属性
-
Object.defineProperty()方法
- 允许通过属性描述对象,定义或修改一个属性,然后返回修改后的对象,接受三个参数
- object:属性所在的对象
- propertyName:字符串,表示属性名
- attributesObject:属性描述对象
- var obj = Object.defineProperty({}, 'p', { value: 123, writable: false, enumerable: true, configurable: false });
-
Object.defineProperties()方法
- 可以一次性定义或修改多个属性
- var obj = Object.defineProperties({}, { p1: { value: 123, enumerable: true }, p2: { value: 'abc', enumerable: true }, p3: { get: function () { return this.p1 + this.p2 }, enumerable:true, configurable:true } });
- Object.defineProperty()和Object.defineProperties()参数里面的属性描述对象,writable、configurable、enumerable这三个属性的默认值都为false
-
一旦定义了取值函数get(或存值函数set),就不能将writable属性设为true,或者同时定义value属性,否则会报错
-
实例对象的propertyIsEnumerable()方法返回一个布尔值,用来判断某个属性是否可遍历。注意,这个方法只能用于判断对象自身的属性,对于继承的属性一律返回false
-
拷贝对象
- 问题在于,如果遇到存取器定义的属性,会只拷贝值,可以通过Object.defineProperty方法来拷贝属性
- var extend = function (to, from) { for (var property in from) { if (!from.hasOwnProperty(property)) continue; Object.defineProperty( to, property, Object.getOwnPropertyDescriptor(from, property) ); }
return to; }
extend({}, { get a(){ return 1 } }) // { get a(){ return 1 } })
-
控制状态
-
JavaScript 提供了三种冻结方法,最弱的一种是Object.preventExtensions,其次是Object.seal,最强的是Object.freeze
-
Object.preventExtensions方法可以使得一个对象无法再添加新的属性
- Object.isExtensible方法用于检查一个对象是否使用了Object.preventExtensions方法
-
Object.seal方法使得一个对象既无法添加新属性,也无法删除旧属性
- 只是禁止新增或删除属性,并不影响修改某个属性的值
- Object.isSealed方法用于检查一个对象是否使用了Object.seal方法,这时,Object.isExtensible方法也返回false
-
Object.freeze方法可以使得一个对象无法添加新属性、无法删除旧属性、也无法改变属性的值,使得这个对象实际上变成了常量
- Object.isFrozen方法用于检查一个对象是否使用了Object.freeze方法
- 使用Object.freeze方法以后,Object.isSealed将会返回true,Object.isExtensible返回false
-
漏洞:可以通过改变原型对象,来为对象增加属性
-
局限是,如果属性值是对象,上面这些方法只能冻结属性指向的对象,而不能冻结对象本身的内容。
-
Array对象
-
会自动扩容,当一个值放在超过数组长度的位置上,数组会重新计算长度,length属性可以设置(清空数组)
-
构造函数
-
Array是 JavaScript 的原生对象,同时也是一个构造函数,可以用它生成新的数组
- var arr = new Array(2); arr.length // 2
- Array构造函数的参数2,表示生成一个两个成员的数组,每个位置都是空值
- 如果没有使用new,运行结果也是一样的
- Array作为构造函数,行为很不一致。不建议使用它生成新数组,直接使用数组字面量是更好的做法
- // bad var arr = new Array(1, 2);
-
// good var arr = [1, 2];
-
静态方法
- isArray方法返回一个布尔值,表示参数是否为数组。它可以弥补typeof运算符的不足
-
实例方法
-
valueOf方法
- 是一个所有对象都拥有的方法,表示对该对象求值。不同对象的valueOf方法不尽一致,数组的valueOf方法返回数组本身
-
toString方法
- 也是对象的通用方法,数组的toString方法返回数组的字符串形式
-
插入 删除
-
push方法 尾入
- 用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度
- 改变原数组
-
pop方法 尾出
- 用于删除数组的最后一个元素,并返回该元素。
- 改变原数组
- 对空数组使用pop方法,不会报错,而是返回undefined
- push和pop结合使用,就构成了“后进先出”的栈结构(stack)
-
shift()方法 头出
- 用于删除数组的第一个元素,并返回该元素。
- 改变原数组
- shift()方法可以遍历并清空一个数组
- push()和shift()结合使用,就构成了“先进先出”的队列结构(queue)
-
unshift()方法 头入
- 用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。
- 改变原数组
-
splice()方法
-
用于删除原数组的一部分成员,并可以在删除的位置添加新的数组成员,返回值是被删除的元素组成的数组
- 删除、插入、替换、截取
-
改变原数组
-
第一个参数是删除的起始位置(从0开始), 第二个参数是被删除的元素个数。 如果后面还有更多的参数,则表示这些就是要被插入数组的新元素
-
起始位置如果是负数,就表示从倒数位置开始删除
-
如果只是单纯地插入元素,splice方法的第二个参数可以设为0
-
如果只提供第一个参数,等同于将原数组在指定位置拆分成两个数组(删除到最后一个元素)
-
-
-
合并
-
join()方法 数组内
-
以指定参数作为分隔符,将所有数组成员连接为一个字符串返回。如果不提供参数,默认用逗号分隔
-
如果数组成员是undefined或null或空位,会被转成空字符串
-
通过call方法,这个方法也可以用于字符串或类似数组的对象
- Array.prototype.join.call('hello', '-') // "h-e-l-l-o"
-
-
concat方法 数组间
-
用于多个数组的合并。它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变
- ['hello'].concat(['world'], ['!']) // ["hello", "world", "!"]
-
concat也接受其他类型的值作为参数,添加到目标数组尾部
- [].concat({a: 1}, {b: 2}) // [{ a: 1 }, { b: 2 }]
-
如果数组成员包括对象,concat方法返回当前数组的一个浅拷贝
-
-
-
排序
-
reverse方法
- 用于颠倒排列数组元素,返回改变后的数组
- 改变原数组
-
sort方法
-
对数组成员进行排序,默认是按照字典顺序排序
-
改变原数组
-
按照自定义方式排序,可以传入一个函数作为参数
-
sort的参数函数本身接受两个参数,表示进行比较的两个数组成员。 如果该函数的返回值大于0,表示第一个成员排在第二个成员后面; 其他情况下,都是第一个元素排在第二个元素前面
- 注意,自定义的排序函数最好返回数值
-
-
-
提取
-
slice()方法
-
用于提取目标数组的一部分,返回一个新数组,原数组不变
-
第一个参数为起始位置(从0开始,会包括在返回的新数组之中), 第二个参数为终止位置(但该位置的元素本身不包括在内)。
-
如果省略第二个参数,则一直返回到原数组的最后一个成员
-
如果slice()方法的参数是负数,则表示倒数计算的位置
-
slice()没有参数,实际上等于返回一个原数组的拷贝
- 重要应用,是将类似数组的对象转为真正的数组
-
-
-
迭代: 1)在每一项元素上运行的函数 2)运行该函数的作用域对象(可选) 都不修改原数组
-
map
- 返回函数返回值组成的数组
- 1)接受一个函数作为第一个参数。该函数调用时,map方法向它传入三个参数:当前成员、当前位置和数组本身
- 2)还可以接受第二个参数,用来绑定回调函数内部的this变量
- 如果数组有空位,map方法的回调函数在这个位置不会执行,会跳过数组的空位
-
forEach
- 对每个元素运行函数,没有返回值
- 用法与map方法一致,参数是一个函数,该函数同样接受三个参数:当前值、当前位置、整个数组;接受第二个参数,绑定参数函数的this变量
- forEach方法也会跳过数组的空位
-
filter
- 返回函数返回值为true组成的数组
- 用法与map方法一致,参数是一个函数,该函数同样接受三个参数:当前值、当前位置、整个数组;接受第二个参数,绑定参数函数的this变量
-
every
- 函数返回值都为true,就为true
-
some
- 函数返回值任一为true,就为true
-
-
累积方法 reduce reduceRight
-
依次处理数组的每个成员,最终累计为一个值,并返回该累积变量
-
参数函数的四个参数: ·累积变量,默认为数组的第一个成员 ·当前变量,默认为数组的第二个成员 ·当前位置(从0开始)索引 ·原数组对象
- 前两个是必须的,后两个则是可选的
-
如果要对累积变量指定初值,可以把它放在reduce方法和reduceRight方法的第二个参数,第二个参数相当于设定了默认值,处理空数组时尤其有用
-
还可以用来做一些遍历相关的操作。比如,找出字符长度最长的数组成员
- function findLongest(entries) { return entries.reduce(function (longest, entry) { return entry.length > longest.length ? entry : longest; }, ''); }
-
-
findLongest(['aaa', 'bb', 'c']) // "aaa"
- 索引
- indexOf方法
- 返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1
- 还可以接受第二个参数,表示搜索的开始位置
- lastIndexOf方法
- 返回给定元素在数组中最后一次出现的位置,如果没有出现则返回-1
- 链式使用
- 上面这些数组方法之中,有不少返回的还是数组,所以可以链式使用
包装对象
-
三种原始类型的值——数值、字符串、布尔值——在一定条件下,也会自动转为对象,也就是原始类型的“包装对象”(wrapper)
- var v1 = new Number(123); var v2 = new String('abc'); var v3 = new Boolean(true);
typeof v1 // "object" typeof v2 // "object" typeof v3 // "object"
v1 === 123 // false v2 === 'abc' // false v3 === true // false
-
“包装对象”,指的是与数值、字符串、布尔值分别相对应的Number、String、Boolean三个原生对象
-
设计目的,首先是使得“对象”这种类型可以覆盖 JavaScript 所有的值,整门语言有一个通用的数据模型,其次是使得原始类型的值也有办法调用自己的方法
-
Number、String和Boolean这三个原生对象,如果不作为构造函数调用(即调用时不加new),而是作为普通函数调用,常常用于将任意类型的值转为数值、字符串和布尔值
- // 字符串转为数值 Number('123') // 123
// 数值转为字符串 String(123) // "123"
// 数值转为布尔值 Boolean(123) // true
-
实例方法
-
valueOf()方法
-
返回包装对象实例对应的原始类型的值
- new Number(123).valueOf() // 123 new String('abc').valueOf() // "abc" new Boolean(true).valueOf() // true
-
-
toString()方法
-
返回对应的字符串形式
- new Number(123).toString() // "123" new String('abc').toString() // "abc" new Boolean(true).toString() // "true"
-
-
-
原始类型与实例对象 的自动转换
- 某些场合,原始类型的值会自动当作包装对象调用,即调用包装对象的属性和方法。这时,JavaScript 引擎会自动将原始类型的值转为包装对象实例,并在使用后立刻销毁实例
- 自动转换生成的包装对象是只读的,无法修改
- 调用结束后,包装对象实例会自动销毁
-
自定义方法
-
在它的原型对象String.prototype上定义方法和属性,供原始类型的值直接调用
- Number.prototype.double = function () { return this.valueOf() + this.valueOf(); };
-
(123).double() // 246
-
Boolean对象
-
作为构造函数,它主要用于生成布尔值的包装对象实例
-
注意,false对应的包装对象实例,布尔运算结果也是true
- new Boolean(false);// true new Boolean(false).valueOf();// false
-
作为类型转换函数
- 可以单独使用,将任意值转为布尔值。这时Boolean就是一个单纯的工具方法
- Boolean(undefined) // false Boolean(null) // false Boolean(0) // false Boolean('') // false Boolean(NaN) // false
- Boolean(1) // true Boolean('false') // true Boolean([]) // true Boolean({}) // true Boolean(function () {}) // true Boolean(/foo/) // true
- 使用双重的否运算符(!)也可以将任意值转为对应的布尔值
-
对于一些特殊值,Boolean对象前面加不加new,会得到完全相反的结果,必须小心
-
-
Number对象
-
Number对象是数值对应的包装对象,可以作为构造函数使用,也可以作为工具函数使用
- 作为构造函数时,它用于生成值为数值的对象
- 作为工具函数时,它可以将任何类型的值转为数值
-
静态属性
-
Number.POSITIVE_INFINITY
- 正的无限,指向Infinity
-
Number.NEGATIVE_INFINITY
- 负的无限,指向-Infinity
-
Number.NaN
- 表示非数值,指向NaN
-
Number.MIN_VALUE
- 表示最小的正数(即最接近0的正数,在64位浮点数体系中为5e-324),相应的,最接近0的负数为-Number.MIN_VALUE
-
Number.MAX_SAFE_INTEGER
- 表示能够精确表示的最大整数,即9007199254740991
-
Number.MIN_SAFE_INTEGER
- 表示能够精确表示的最小整数,即-9007199254740991
-
-
实例方法
-
toString方法
- 用来将一个数值转为字符串形式
- 可以接受一个参数,表示输出的进制。如果省略这个参数,默认将数值先转为十进制,再输出字符串;否则,就根据参数指定的进制,将一个数字转化成某个进制的字符串
-
toFixed()方法
- 先将一个数转为指定位数的小数,然后返回这个小数对应的字符串
- 由于浮点数的原因,小数5的四舍五入是不确定的,使用的时候必须小心
-
toExponential方法
- 用于将一个数转为科学计数法形式
-
toPrecision()方法
- 用于将一个数转为指定位数的有效数字
- 该方法用于四舍五入时不太可靠,跟浮点数不是精确储存有关
-
toLocaleString()方法
-
接受一个地区码作为参数,返回一个字符串,表示当前数字在该地区的当地书写形式
- (123).toLocaleString('zh-Hans-CN-u-nu-hanidec') // "一二三"
-
如果style属性的值为currency,则可以搭配currency属性,输出指定格式的货币字符串形式
- (123).toLocaleString('zh-Hans-CN', { style: 'currency', currency: 'CNY' }) // "¥123.00"
-
省略了参数,则由浏览器自行决定如何处理,通常会使用操作系统的地区设定
-
-
自定义方法
- Number.prototype对象上面可以自定义方法,被Number的实例继承
-
-
-
String对象
-
字符串对象是一个类似数组的对象(很像数组,但不是数组)
-
除了用作构造函数,String对象还可以当作工具方法使用,将任意类型的值转为字符串
-
静态方法
-
fromCharCode()方法
- 参数是一个或多个数值,代表 Unicode 码点,返回值是这些码点组成的字符串
- 不支持 Unicode 码点大于0xFFFF的字符
- 根本原因在于,码点大于0xFFFF的字符占用四个字节,而 JavaScript 默认支持两个字节的字符
-
-
实例属性
- length属性返回字符串的长度
-
实例方法
-
字符
-
charAt方法
- 返回指定位置的字符,参数是从0开始编号的位置
- 完全可以用数组下标替代
- 如果参数为负数,或大于等于字符串的长度,charAt返回空字符串
-
charCodeAt方法
- 返回字符串指定位置的 Unicode 码点(十进制表示),相当于String.fromCharCode()的逆操作
- 只返回两个字节的字符的码点
- 如果遇到码点大于 65536 的字符(四个字节的字符),必需连续使用两次charCodeAt,不仅读入charCodeAt(i),还要读入charCodeAt(i+1),将两个值放在一起,才能得到准确的字符
-
-
连接 分割
-
concat方法
- 用于连接两个字符串,返回一个新字符串,不改变原字符串
- 如果参数不是字符串,concat方法会将其先转为字符串,然后再连接
-
split方法
- 按照给定规则分割字符串,返回一个由分割出来的子字符串组成的数组
- 如果分割规则为空字符串,则返回数组的成员是原字符串的每一个字符
- 如果满足分割规则的两个部分紧邻着(即两个分割符中间没有其他字符),则返回数组之中会有一个空字符串
- 如果满足分割规则的部分处于字符串的开头或结尾(即它的前面或后面没有其他字符),则返回数组的第一个或最后一个成员是一个空字符串
- 还可以接受第二个参数,限定返回数组的最大成员数
-
-
提取
-
slice方法
- 用于从原字符串取出子字符串并返回,不改变原字符串。
- 第一个参数是子字符串的开始位置 第二个参数是子字符串的结束位置(不含该位置) 左闭右开
- 如果省略第二个参数,则表示子字符串一直到原字符串结束
- 如果参数是负值,表示从结尾开始倒数计算的位置,即该负值加上字符串长度
- 如果第一个参数大于第二个参数,slice方法返回一个空字符串
-
substring方法
- 用于从原字符串取出子字符串并返回,不改变原字符串,跟slice方法很相像
- 第一个参数是子字符串的开始位置 第二个参数是子字符串的结束位置(不含该位置) 左闭右开
- 如果省略第二个参数,则表示子字符串一直到原字符串的结束
- 如果第一个参数大于第二个参数,substring方法会自动更换两个参数的位置
- 如果参数是负数,substring方法会自动将负数转为0
- 不建议使用substring方法,应该优先使用slice
-
substr方法
- 用于从原字符串取出子字符串并返回,不改变原字符串,跟slice和substring方法的作用相同
- 第一个参数是子字符串的开始位置(从0开始计算) 第二个参数是子字符串的长度
- 如果省略第二个参数,则表示子字符串一直到原字符串的结束
- 如果第一个参数是负数,表示倒数计算的字符位置。如果第二个参数是负数,将被自动转为0,因此会返回空字符串
-
-
索引
-
indexOf方法
- 用于确定一个字符串在另一个字符串中第一次出现的位置,返回结果是匹配开始的位置。如果返回-1,就表示不匹配
- 还可以接受第二个参数,表示从该位置开始向后匹配
-
lastIndexOf方法
- 用法跟indexOf方法一致,主要的区别是lastIndexOf从尾部开始匹配,indexOf则是从头部开始匹配
- 第二个参数表示从该位置起向前匹配
-
-
转换
-
trim方法
- 用于去除字符串两端的空格,返回一个新字符串,不改变原字符串
- 去除的不仅是空格,还包括制表符(\t、\v)、换行符(\n)和回车符(\r)
-
toLowerCase方法
- 用于将一个字符串全部转为小写
- 返回一个新字符串,不改变原字符串
-
toUpperCase方法
- 全部转为大写
- 返回一个新字符串,不改变原字符串
-
-
正则
-
match方法
- 用于确定原字符串是否匹配某个子字符串,返回一个数组,成员为匹配的第一个字符串。如果没有找到匹配,则返回null
- 返回的数组还有index属性和input属性,分别表示匹配字符串开始的位置和原始字符串
-
search方法
- 用法基本等同于match,但是返回值为匹配的第一个位置。如果没有找到匹配,则返回-1
-
replace方法
- 用于替换匹配的子字符串,一般情况下只替换第一个匹配(除非使用带有g修饰符的正则表达式)
-
-
比较
-
localeCompare方法
- 用于比较两个字符串,返回一个整数, 如果小于0,表示第一个字符串小于第二个字符串; 如果等于0,表示两者相等; 如果大于0,表示第一个字符串大于第二个字符串
-
-
-
源自: wangdoc.com/ 、 红宝书 、 es6.ruanyifeng.com/