1.变量
1.1 变量规则
- 变量只能包含字母、下划线、美元符号或数字;
- 第一个字符不能是数字。
- ECMAScript 的变量是松散类型的 —— 可以用来保存任何类型的数据,即每个变量仅仅是一个用于保存值的占位符而已。
1.2 定义变量
1.2.1 var —— ES5及之前使用
var name = "jane";
var 没有块级作用域,只有函数级作用域和全局作用域:
- 函数级作用域:
在函数内用 var 定义一个变量,则该变量为定义该变量的作用域中的局部变量,函数退出后则销毁。- 全局作用域:
省略 var ,则为全局变量,在函数外的任何地方都可被访问到(不推荐,难维护,且在严格模式下会抛出错误)。
1.2.2 let & const —— ES6 新增
ES6 中增加了 let 和 const :
let 用来定义变量,定义后可更改;
const 用来定义常量,定义后不可更改。
let 和 var 的区别:
- let 是块级作用域,var 是函数级作用域,let 的作用域更小;
- let 无变量提升。
下面定义的变量,在上面使用会报错;
var有变量提升,下面定义的变量,在上面值为undefined,不会报错。- let 同一个变量只能声明一次,而 var 可声明多次。
2. 数据类型
JavaScript 中有基本类型和引用类型两类共七种数据类型。
undefined 用于未定义的值 —— 只有一个 undefined 值的独立类型。
null 用于未知的值 —— 只有一个 null 值的独立类型。
boolean 用于 true 和 false。
number 数字:整数或浮点数。
string 字符串:一个字符串可以包含一个或多个字符。
symbol 用于唯一的标识符。
object 更复杂的数据结构,用于储存数据集合和更复杂的实体。
2.1 基本类型
2.1.1 Undefined
定义:
let a = undefined;
使用var声明变量但未初始化,或初始化其值为undefined。
(var message);alert(message);//undefined
2.1.2 Null
定义:
let a = null;
空对象指针,用于在将来保存对象。这样只要检查变量是否等于null,就知道这个变量内是否已经保存了一个对象的引用。
注:
alert(null == undefined); //true
alert(null === undefined); //false
2.1.3 Boolean
定义:
let a = true;
let b = false;
ECMAScript 中所有类型的值都有与这两个 Boolean 值等价的值,可使用转型函数 Boolean() 进行转换。
- 非空字符串、非零数值、任何对象都是 true;
Boolean('111') === true
- 空字符串、数值零或NaN、null、undefined 都是false。
Boolean('text') === true//true
Boolean(2) === true//true
Boolean({}) === true//true
Boolean({name: 'jane'}) === true//true
- 流控制语句(如if)会自动执行 Boolean 转换。
var message = 'hello';
if(message){alert('value is true');} //message被自动转换为对应的 Boolean 值(true)。
2.1.4 Number
2.1.4.1 定义
//整数
let num = 12;
//八进制:第一位为0,后面数值若超出范围,则前导零被忽略,将后面数值按十进制解析
let num1 = 070;//八进制的56
let num2 = 079;//无效的八进制数值,解析为79
//十六进制:前两位为0x,后跟十六进制数字
let num3 = 0xa; //十六进制的10
let num4 = 0x1f; //十六进制的31
2.1.4.2 NaN
NaN(非数值)——特殊的数值,本该返回数值的操作数未返回数值的情况,不抛出错误。
1.任何涉及 NaN 的操作都会返回 NaN ,NaN 与任何值都不相等。
alert(NaN == NaN); //false
2.isNaN()
isNaN() 函数收到一个值后,会尝试将这个值转换为数值,可以转换则返回 false,否则返回 true。
isNaN(true)//false
isNaN(false)//false
isNaN(1)//false
isNaN('1')//false
isNaN('2')//false
isNaN(null)//false
isNaN(undefined)//true
isNaN({})//true
isNaN(function(){})//true
isNaN('2/0')//true
2.1.4.3 数值转换
Number():将任何类型转换为数值;
parseInt():将字符串转换为整数;
parseFloat():将字符串转换为浮点数;
Number()
var num1 = Number("Hello world!"); //NaN
var num2 = Number(""); //0
var num3 = Number("000011"); //11
var num4 = Number(true); //1
parseInt()
var num1 = parseInt("1234blue"); // 1234
var num2 = parseInt(""); // NaN
var num4 = parseInt(22.5); // 22
var num6 = parseInt("70"); // 70
var num8 = parseInt("s2131"); // NaN
var num = parseInt("0xAF", 16); //175
var num1 = parseInt("AF", 16); //175
var num2 = parseInt("AF"); //NaN
指定基数转换:
var num1 = parseInt("10", 2); //2 (按二进制解析)
var num2 = parseInt("10", 8); //8 (按八进制解析)
var num3 = parseInt("10", 10); //10 (按十进制解析)
var num4 = parseInt("10", 16); //16 (按十六进制解析)
parseFloat()
从第一个字符开始解析,始终忽略前导零
var num1 = parseFloat("1234blue"); //1234 (整数)
var num2 = parseFloat("0xA"); //0
var num3 = parseFloat("22.5"); //22.5
var num4 = parseFloat("22.34.5"); //22.34
var num5 = parseFloat("0908.5"); //908.5
var num6 = parseFloat("3.125e7"); //31250000
2.1.5 String
- String 类型用于表示由零或多个16 位Unicode 字符组成的字符序列,即字符串。
- 字符串可以由双引号(")或单引号(')表示,但前后符号应保持一致。
2.1.5.1 定义
var firstName = "Nicholas";
var lastName = 'Zakas';
2.1.5.2 toString()
- 数值、布尔值、对象和字符串都有一个 toString() 方法,返回一个对应字符串。
let num = 2; num.toString();//"2"
let flag = true; flag.toString();//"true"
let text = 'hello'; text.toString();//"hello"
数值的 toString() 方法可以传一个参数:输出数值的基数,如下:
var num = 10;
alert(num.toString()); // "10" 不传,默认为10
alert(num.toString(2)); // "1010"
alert(num.toString(8)); // "12"
alert(num.toString(10)); // "10"
alert(num.toString(16)); // "a"
- 对象的 toString() 方法
let obj = {name: 'jane'};
obj.toString() //"[object Object]"`
null 和 undefined 没有 toString() 方法。
String():
不知道要转换的值是不是 null 或 undefined 时,可以使用转型函数 String(),这个函数可以将任何类型的值转换为字符串。String()遵循下列转换规则:
- 如果值有toString()方法,则调用该方法(没有参数)并返回相应的结果;
- 如果值是null,则返回"null";
- 如果值是undefined,则返回"undefined"。
2.1.5.3 String 的其他常用方法
let testStr = 'testStr';
testStr.substr(1,3) //"est" 截取 testStr 从第1个开始,取3个
testStr.substring(1,4) //"est" 截取 testStr 从第1个开始,到第4个,不包含第4个
testStr.slice(1,4) //"est" 截取 testStr 从第1个开始,到第4个,不包含第4个
testStr.substring(2,0) //"te"
testStr.slice(2,0) //""
2.2 引用类型 —— 对象
JavaScript 中,对象是及其重要的一点。其他六种是原始类型,他们的值只包含一种东西,而对象是用来存储键值对和更复杂的实体的。
2.2.1 创建对象
//“构造函数”语法创建(后面会详细介绍构造函数)
let info = new Object()
//“字面量”语法创建 —— 常用
let info = {
name: 'jane', //键"name",值"jane"
age: 17 //键"age",值17
}
2.2.2 对象操作
先创建一个对象
let info = {
name: 'jane', //键"name",值"jane"
age: 17 //键"age",值17
}
2.2.2.1 添加属性
//使用 .
info.address = 'beijing'//"beijing",此时的 info:{name: "jane", age: 17, address: "beijing"}
//使用 []
info['friend'] = 'tom'//"tom",此时的 info:{name: "jane", age: 17, address: "beijing", friend: "tom"}
2.2.2.2 计算属性
计算属性 —— 在对象字面量中使用方括号,方括号内可以是任何属性名或变量
let s = 'sch'.concat('ool);
info[s] = 'HIT';//此时的 info:{name: "jane", age: 17, school: "HIT"}
2.2.2.3 删除属性
delete info.age//true,此时的 info:{name: "jane", address: "beijing", friend: "tom"}
2.2.2.4 获取对象属性值
//使用 .
info.address//"beijing"
//使用 []
info['friend']//"tom"
2.2.2.5 对象的可枚举与不可枚举
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性。
Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。
(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
语法:
Object.getOwnPropertyDescriptor(obj, prop)
//obj:需要查找的目标对象, prop:目标对象内属性名称
let info = {
name: 'jane', //键"name",值"jane"
age: 17 //键"age",值17
}
let des = Object.getOwnPropertyDescriptor(info, 'name')
//des
{
value: "jane", //该属性的值
writable: true, //当且仅当属性的值可以被改变时为true
enumerable: true, //当且仅当指定对象的属性可以被枚举出时,为 true
configurable: true //当且仅当指定对象的属性描述可以被改变或者属性可被删除时,为true
}
不可枚举(enumerable:false): 有以下操作会忽略不可枚举的属性。
- for...in:不可枚举的属性不会被遍历。
- Object.keys():不可枚举的属性不会被遍历。
- JSON.stringify():不可枚举的属性不会被转换为字符串。
- Object.assign(): 不可枚举的属性不会被复制。
2.2.3 对象常用方法
先创建一个对象
let info = {
name: 'jane', //键"name",值"jane"
age: 17 //键"age",值17
}
2.2.3.1 属性检查
1. hasOwnProperty —— object.hasOwnProperty(propertyName),propertyName 必须为字符串
info.hasOwnProperty('name') //true
info.hasOwnProperty('language') //false
2. "key" in object —— key 为要检测的属性
"name" in info //true
"language" in info //false
2.2.3.2 对象遍历
先创建一个对象
let info = {
name: 'jane', //键"name",值"jane"
age: 17 //键"age",值17
}
Object.defineProperty(info, 'name', {
enumerable: true //可枚举
});
Object.defineProperty(info, 'age', {
enumerable: false //可枚举
});
//在原型链上添加属性 —— 先了解,原型链部分后再看
Object.defineProperty(info, 'eg1', {
value: 'view',
enumerable: true //可枚举
});
Object.defineProperty(info, 'eg2222', {
value: 'unview',
enumerable: false //不可枚举
})
1. for ... in —— 先遍历对象自身的可枚举属性,再遍历继承的可枚举属性。
for (var index in info) {
console.log('key:', index, 'value:', info[index])
}
//result 只遍历了自身及原型链上的可枚举属性
key: name value: jane
key: eg1 value: view
2. Object.keys() —— 返回自身及原型链上的可枚举属性组成的数组,可通过遍历数组来遍历对象。
Object.keys(info)
//result 返回自身及原型链上的可枚举属性组成的数组
["name", "eg1"]
Object.keys(info).forEach(item=>{
console.log(item, info[item])
})
//result
name jane
eg1 view
3. Reflect.ownKeys() —— 返回自身及原型链上所有属性组成的数组,可通过遍历数组来遍历对象。
Reflect.ownKeys(info)
//result 返回自身及原型链上的所有属性组成的数组,包括不可枚举属性
["name", "age", "eg1", "eg2222"]
Reflect.ownKeys(info).forEach(item=>{
console.log(item, info[item])
})
//result
name jane
age 17
eg1 view
eg2222 unview
4. Object.getOwnPropertyNames() —— 返回自身及原型链上所有属性组成的数组(包括不可枚举属性),可通过遍历数组来遍历对象。
Object.getOwnPropertyNames(info)
//result 返回自身及原型链上的所有属性组成的数组,包括不可枚举属性
["name", "age", "eg1", "eg2222"]
Object.getOwnPropertyNames(info).forEach(item=>{
console.log(item, info[item])
})
//result
name jane
age 17
eg1 view
eg2222 unview
2.3 变量赋值
基本类型数据赋值:
从一个基本类型变量向另一个变量赋值时,会在内存中新建一个地址,存放新的变量和复制过来的值;
let a = 1;
let b = a;
b = 3;
console.log(a, b)// => a = 1; b = 3; a 和 b 的值存在于内存中的两个地址,互不影响。
引用类型数据赋值:
从一个引用类型变量向另一个变量赋值时,同上,但引用类型的值,实际上是一个指针,与初始变量指向同一个堆内存的对象。因此,这两个变量会互相影响。
let s = {name:"jane"};let y = s; y.name="tom";console.log(s, y)
let a = {name: "jane"};
let b = a;
b.name = "tom";
b.city="cc";
console.log(a, b) // => {name: "tom", city: "cc"} {name: "tom", city: "cc"} a 和 b 的值存在于内存中的两个地址,但值是同一个指针,指向同一个内存中的对象,属性的改变会相互影响。
3. 函数 & 原型 & 继承
3.1 函数
3.1.1 函数声明:
//语法:
function 函数名(参数1, 参数2,...,参数N) {
函数体
}
//eg:
function sayHi(name, message) {
alert("Hello " + name + "," + message);
}
3.1.2 函数调用
//语法
functionName(arg0, arg1,...,argN);
//eg:
sayHi("Nicholas", "how are you today?");//弹出 "Hello Nicholas,how are you today?"
3.1.3 变量作用域
- 函数内声明的变量,只在该函数内部可见。
function sayHi() {
let name = 'jane';
let age = 18;
alert(name + ":" + age);
}
console.log('age:', age);//age is not defined
- 函数外声明的变量,函数可以访问,也可以修改。
let age = 20;
function sayHi() {
let name = 'jane';
age = 18;
console.log(name + ":" + age);
}
sayHi() //jane:18
3.1.4 函数返回值
任何函数、任何时候都可以通过 return 语句后跟要返回的值来是实现返回值。函数会在执行完 return 语句后停止并立即退出。因此,return 之后的任何代码都永远不会执行。
function sum(num1, num2) {
return num1 + num2;
}
let result = sum(5, 10); // result 为15
注:
- return 可以不带任何返回值。则函数将返回 undefined 值。一般用在需要提前停止函数执行,又不需要返回值的情况下。
- 推荐:要么让函数始终都有返回值,要么就永远都不要有返回值,否则会给代码调试带来不便。
严格模式下的限制: 发生以下情况,会导致语法错误,代码无法执行。
- 不能把函数/参数命名为eval 或arguments;
- 不能出现两个命名参数同名的情况。
3.1.5 参数
参数不是必需的,可以不传,个数及类型,即使个数与定义的个数不同,也不会报错。因为参数在内部使用一个数组来表示的。
函数体内部可以通过 arguments 对象来访问这个参数数组,从而获取传递过来的每一个参数。
function sayHi(name, message) {
alert("Hello " + name + "," + message);
}
sayHi('jane', 'nice to meet you!')//Hello jane,nice to meet you!
//重写
function sayHi() {
alert("Hello " + arguments[0] + "," + arguments[1]);
}
sayHi('jane', 'nice to meet you!')//Hello jane,nice to meet you!
- arguments 对象的长度由传入函数的参数决定;
- 没有传递值的命名参数自动被赋予 undefined 值,类似于定义了变量但未初始化。
严格模式下:
重写arguments 的值会导致语法错误(代码将不会执行)
3.1.6 ES6 函数新特性
1. 参数默认值
声明函数时直接为参数设置默认值,若调用函数时未赋值,则参数的值为默认值。
function sayHi(name, message='nice to meet you!') {
console.log("Hello " + name + "," + message);
}
sayHi('jane', 'yeah!') //Hello jane,yeah!
sayHi('tom') //Hello tom,nice to meet you!
若默认值为表达式,则执行时才计算其值
let num = 10;
function sayHi(name, age=num+8) {
console.log(name + ", age:" + age);
num+=10;
}
sayHi('jane') //jane, age:18
sayHi('tom') //tom, age:28
sayHi('lucy', 12) //lucy, age:12
2. rest 参数
...变量名,用于获取函数的剩余参数,将其放在数组中。
rest 参数必须是最后一个参数,否则会报错。
function sum(...nums){
let sum = 0;
nums.forEach(item => {
sum += item;
})
return sum
}
let result1 = sum() //0
let result2 = sum(1,2,3,4) //10
3. 扩展运算符
...变量名,用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。
//对象
let obj1 = {name: 'jane', age: 18};
let obj2 = {color: 'yellow', ...obj1} //obj2: {color: "yellow", name: "jane", age: 18}
//等价于
let obj3 = Object.assign({color: 'yellow'}, obj1) //obj3: {color: "yellow", name: "jane", age: 18}
//数组
let arr1 = [1,2,3]
let arr2 = [4,5,6]
let arr3 = [7,8,9]
let arr4 = [...arr1, ...arr2, ...arr3] //[1, 2, 3, 4, 5, 6, 7, 8, 9]
//等价于
let arr5 = arr1.concat(arr2, arr3) //[1, 2, 3, 4, 5, 6, 7, 8, 9]
4. 箭头函数
//箭头函数定义
let sum = (a,b) => {
return a+b
}
//等同于
let sum = function(a,b) {
return a+b
}
- 箭头函数的 this 指向的是定义时的 this 对象,而不是执行时的 this 对象
- 箭头函数是匿名函数 ,不能作为构造函数
- 箭头函数不可以使用 arguments,可使用 rest 参数
5. name
ES6 的 name 会返回函数名。
function sum(...nums){
let sum = 0;
nums.forEach(item => {
sum += item;
})
return sum
}
console.log(sum.name); //"sum"
3.2 构造函数
1. 构造函数: 一种特殊的函数。
- 主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new操作符一起使用在创建对象的语句中。
- 我们可以使用构造函数来创建多个类似的对象,而免于代码重复。
- JavaScript 内置了一些构造函数
2. 构造函数特点
- 首字母大写(通常约定)
- 只能用 new 操作符来执行
- 内部使用的 this 对象,会指向即将生成的实例对象
3. 实例化一个对象的过程
new 一个新对象 => this 指向这个新对象 => 执行代码(对 this 赋值)=> 返回 this
function Person(name, age) {
this.name = name;
this.age = age;
this.displayInfo = function() {
console.log(`name: ${name}, age: ${age}`)
}
}
let jane = new Person('jane', 12)
jane.displayInfo() //name: jane, age: 12
let tom = new Person('tom', 18)
tom.displayInfo() //name: tom, age: 18
JavaScript 内置了很多构造函数,如:
Object、Function、Array、RegExp、Date、Number、String、Boolean。
Object 在 2.2 引用类型 已经详细介绍过了。
在 JavaScript 中,Function、Array、RegExp、Date、Number、String、Boolean 是构造函数,也有自己的构造函数,他们的构造函数就是 Object,即 Object 是其他构造函数的构造函数。
后面还会介绍一个 Math 对象,Math 不是对象的类,没有构造函数,但作为 JavaScript 内置的对象,可直接调用来进行数学方法的处理。
3.2.1 Function
3.2.1.1 函数声明:
//1. 构造函数创建 new Function
let 函数名 = new Function(参数1, 参数2,...,参数N, 函数体)
//eg:
let sayHi = new Function("name", "message", "alert('Hello " + name + "," + message)')
sayHi3('jane','hi')//Hello jane,hi
但实际的代码中不使用这种方法来创建函数,还是会用字面量的方法去创建,关于函数的详细介绍,见 3.1 函数
3.2.2 Array
3.2.2.1 创建数组
创建数组:
//使用new Array()
let arr1 = new Array(); //[]
let arr2 = new Array(5); //[empty × 5]
let arr3 = new Array('beijing','tianjin',12);//["beijing", "tianjin", 12]
//使用方括号 —— 常用
let arrs1 = [];//[]
let arrs2 = ['beijing','tianjin',12];//["beijing", "tianjin", 12]
数组内的元素可以是任何类型:
let arr = ['beijing', {name: 'jane'}, 12, true, function(){console.log('fn')}];
//0: "beijing"
//1: {name: "jane"}
//2: 12
//3: true
//4: ƒ ()
数组编号从 0 开始:
let arr = ['beijing','tianjin',12];
//arr[0]: 'beijing'
//arr[1]: 'tianjin'
//arr[2]: 12
通过 数组名[下标] 来获取或设置值:
let arr = ['beijing','tianjin',12];
console.log(arr[0]);// 'beijing'
arr[3] = true;
console.log(arr); //["beijing", "tianjin", 12, true]
3.2.2.2 常用方法
判断是否为数组:Array.isArray
//Array.isArray —— 是则返回 true,否责返回 false
let a = [1,2,3];
let b = 'str';
let b = [];
console.log(Array.isArray(a), Array.isArray(b), Array.isArray(c)); // true false true
//unshift —— 在数组最前面添加元素,可添加多个
arr.unshift('a', 'b', 'c')
console.log(arr);//arr: ["a", "b", "c", 1, 2, 3, 4, 5, 6]
添加数组元素:push、unshift
//push —— 在数组最后添加元素,可添加多个
let arr = [1,2,3];
arr.push(4,5,6);
console.log(arr);//arr: [1,2,3,4,5,6]
//unshift —— 在数组最前面添加元素,可添加多个
arr.unshift('a', 'b', 'c')
console.log(arr);//arr: ["a", "b", "c", 1, 2, 3, 4, 5, 6]
删除数组元素:pop、shift
//pop() —— 删除并返回数组的最后一个元素
console.log(arr.pop())//6
console.log(arr);//["a", "b", "c", 1, 2, 3, 4, 5]
//shift() —— 删除并返回数组的第一个元素
console.log(arr.shift()) //a
console.log(arr); //["b", "c", 1, 2, 3, 4, 5]
替换数组中的元素:splice
//splice(start, num, newitem) —— 把 arr[start] 开始的 num 个,替换成后面的元素,不改变原数组
console.log(arr) //[5, 4, 3, 2, 1, "c", "b"]
console.log(arr.splice(2,3,'jane')) //[3, 2, 1]
console.log(arr) //[5, 4, "jane", "c", "b"]
console.log(arr.splice(1,1,'aa','bb', 'cc')) //[4]
console.log(arr) //[5, "aa", "bb", "cc", "jane", "c", "b"]
替换数组中某些元素:fill
//fill(value, start, end)
let arr = [1, 2, 3, 4, 5, 5, 6, "b", "c"]
let result1 = arr.fill('hello');
// result1: ["hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"]
let result2 = arr.fill('hello', 2, 5);
//result2: [1, 2, "hello", "hello", "hello", 5, 6, "b", "c"]
let result3 = arr.fill('hello', 2, );
//result3: [1, 2, "hello", "hello", "hello", "hello", "hello", "hello", "hello"]
取数组中的元素:slice
//slice(start, end) —— 取 [arr[start], ..., arr[end-1]],不改变原数组
console.log(arr) //[5, 4, 3, 2, 1, "c", "b"]
console.log(arr.slice(2,5)) //[3, 2, 1]
console.log(arr) //[5, 4, 3, 2, 1, "c", "b"]
颠倒数组中元素的顺序:reverse
//reverse —— 颠倒数组中元素的顺序
console.log(arr) //["b", "c", 1, 2, 3, 4, 5]
console.log(arr.reverse()) //[5, 4, 3, 2, 1, "c", "b"]
console.log(arr) //[5, 4, 3, 2, 1, "c", "b"]
数组排序:sort
//sort —— 将数组中的元素进行排序
let arrN = [1,2,4,6,12,11]
arrN.sort() //[1, 11, 12, 2, 4, 6]
arrN.sort((a, b)=>{return a-b}) //[1, 2, 4, 6, 11, 12] 从小到大排序
arrN.sort((a, b)=>{return b-a}) //[12, 11, 6, 4, 2, 1] 从大到小排序
数组转换为字符串:toString、join
//1. toString —— 将数组转换为字符串
arr.toString() //"5,4,3,2,1,c,b
//2. join —— 通过指定的分隔符, 把数组的所有元素连接成一个字符串
arr //[5, 4, 3, 2, 1, "c", "b"]
arr.join() //"5,4,3,2,1,c,b"
arr.join('-') //"5-4-3-2-1-c-b"
连接数组:concat
//concat 连接数组,并返回结果,不改变原数组
let arr1 = ['jane', 'tom']
let arr2 = ['green', 'red']
let arr3 = [true, false]
let arr4 = [1, 2, 3]
arr1.concat(arr2) //["jane", "tom", "green", "red"]
arr1.concat(arr2, arr3, arr4) //["jane", "tom", "green", "red", true, false, 1, 2, 3]
查询数组: indexOf、lastIndexOf、find、findIndex、filter
let arr1 = [5, 4, 3, 2, 1, 5, 6, "c", "b"];
//indexOf(item) —— 从前面开始查询,返回查到的第一个 item 的下标值
arr1.indexOf(5) //0
//lastIndexOf(item) —— 从后面开始查询,返回查到的第一个 item 的下标值
arr1.lastIndexOf(5) //5
//includes(item) —— 判断数组中是否存在 item,存在则返回 true,不存在则返回 false
arr1.includes(0) //false
arr1.includes(1) //true
//find —— 遍历数组,返回使函数返回 true 的第一个元素
let arr2 = [
{ name: 'jane', age:12 },
{ name: 'tom', age:13 },
{ name: 'lucy', age:14 },
{ name: 'jimmy', age:12 }
]
let result1 = arr2.find((item, index, array) => {
return item.age === 12
})
console.log('result1:', result1) //result: {name: "jane", age: 12}
//findIndex —— 遍历数组,返回使函数返回 true 的第一个元素的下标
let result2 = arr2.findIndex((item, index, array) => {
return item.age === 12
})
console.log('result2:', result2) //result: 0
//filter —— 遍历数组,返回使函数返回 true 的所有元素组成的数组
let result3 = arr2.filter((item, index, array) => {
return item.age === 12
})
console.log('result3:', result3)
//result3: [{ name: 'jane', age:12 },{ name: 'jimmy', age:12 }]
检查元素:some、every
//some —— 数组中至少有一个元素在执行函数时返回的结果为 true
let arr1 = [1, 2, 3, 4, 5, 5, 6, 7]
let arr2 = [1, 2, 3, 4, 5, 5, 6, 7, 8]
let flag1 = arr1.some((item)=>{
return item > 7
}) //flag: false
let flag2 = arr2.some((item)=>{
return item > 7
}) //flag: true
//every —— 数组中每一个元素在执行函数时返回的结果为 true
let arr1 = [1, 2, 3, 4, 5, 5, 6, 7]
let arr2 = [1, 2, 3, 4, 5, 5, 6, 7, 8]
let flag1 = arr1.every((item)=>{
return item > 7
}) //flag: false
let flag2 = arr2.every((item)=>{
return item > 7
}) //flag: false
let flag3 = arr2.every((item)=>{
return item > 0
}) //flag: true
转换数组:map
let arr = [1, 2, 3, 4, 5, 5, 6, "b", "c"]
let result = arr.map(function(item, index, array) {
return `${item}--hello`;
})
//result: ["1--hello", "2--hello", "3--hello", "4--hello", "5--hello", "5--hello", "6--hello", "b--hello", "c--hello"]
遍历数组:forEach
let arr = [1, 2, 3, 4]
arr.forEach(function(item, index, array) {
console.log(item, index, array)
})
// 1 0 (4) [1, 2, 3, 4]
// 2 1 (4) [1, 2, 3, 4]
// 3 2 (4) [1, 2, 3, 4]
// 4 3 (4) [1, 2, 3, 4]
以上会改变数组的方法:
push、unshift、pop、shift、splice、reverse、sort、fill
3.2.3 Date
Date 为 JavaScript 内置的一个对象,用来存储日期、时间,并提供了一些管理它们的方法。
//创建 Date 对象
let date = new Date()//date: Wed Jun 05 2019 17:10:27 GMT+0800 (中国标准时间)
//getDay:返回 0-6,对应星期日到星期六
date.getDay() //3
//getDate:返回 1-31,对应一个月中的 1-31 号
date.getDate() //5
//getMonth:返回 0-11,对应1-12月
date.getMonth() //5——6月
//getFullYear:以四位数字返回年份
date.getFullYear() //2019
//getHours:返回小时
date.getHours() //17
//getMinutes:返回分钟
date.getMinutes() //10
//getSeconds:返回秒
date.getSeconds() //27
//getMilliseconds:返回毫秒
date.getMilliseconds() //511
//getTime:返回 1970 年 1 月 1 日至今的毫秒数
date.getTime() //1559725827511
Date.now()
有一个特殊的方法 Date.now(),它会返回当前的时间戳,不需要整个 Date 对象。
它相当于 new Date().getTime(),但不会在中间创建一个 Date 对象。因此更快,且不会对垃圾处理造成额外的压力。
Date.now() //1559725827511
3.2.4 RegExp
RegExp 是 JavaScript 内置的正则表达式对象,用来对字符串进行匹配。
3.2.4.1 定义正则表达式
1)字面量语法:/pattern/attributes
2)创建 RegExp 对象的语法:new RegExp(pattern, attributes);
3.2.4.2 参数
- pattern 是一个字符串,指定了正则表达式的模式或其他正则表达式。
- attributes 是一个可选的字符串,包含属性 "g"、"i" 和 "m",分别用于指定全局匹配、区分大小写的匹配和多行匹配。如果 pattern 是正则表达式,而不是字符串,则必须省略该参数。
3.2.4.3 修饰符
i 执行对大小写不敏感的匹配。
g 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。
m 执行多行匹配。
3.2.4.4 方括号——查找某个范围内的字符
[abc] 查找方括号之间的任何字符。(字母、数字、符号、中文均可)
[^abc] 查找任何不在方括号之间的字符。(字母、数字、符号、中文均可)
[0-9] 查找任何从 0 至 9 的数字。
[a-z] 查找任何从小写 a 到小写 z 的字符。
[A-Z] 查找任何从大写 A 到大写 Z 的字符。
[A-z] 查找任何从大写 A 到小写 z 的字符。
[adgk] 查找给定集合内的任何字符。
[^adgk] 查找给定集合外的任何字符。
(red|blue|green) 查找任何指定的选项。(可匹配单词)
3.2.4.5 元字符(Metacharacter)——拥有特殊含义的字符
. 查找单个字符,除了换行和行结束符。
\w 查找单词字符。(数字、字母、下划线)
\W 查找非单词字符。(除数字、字母、下划线的字符,包括中文、特殊符号、空格等)
\d 查找数字。
\D 查找非数字字符。
\s 查找空白字符。
\S 查找非空白字符。
\b 匹配单词边界。
\B 匹配非单词边界。
\0 查找 NUL 字符。
\n 查找换行符。
\f 查找换页符。
\r 查找回车符。
\t 查找制表符。
\v 查找垂直制表符。
\xxx 查找以八进制数 xxx 规定的字符。
\xdd 查找以十六进制数 dd 规定的字符。
\uxxxx 查找以十六进制数 xxxx 规定的 Unicode 字符。
3.2.4.6 量词
n+ => 匹配任何包含至少一个 n 的字符串。
n* => 匹配任何包含零个或多个 n 的字符串。
n? => 匹配任何包含零个或一个 n 的字符串。
n{X} => 匹配包含 X 个 n 的序列的字符串。 a{2} aa
n{X,Y} => 匹配包含 X 至 Y 个 n 的序列的字符串。a{2,4} aa aaa aaaa
n{X,} => 匹配包含至少 X 个 n 的序列的字符串。a{2} aa aaa aaaa aaaaa...
n$ => 匹配任何结尾为 n 的字符串。
^n => 匹配任何开头为 n 的字符串。
?=n => 匹配任何其后紧接指定字符串 n 的字符串。
?!n => 匹配任何其后没有紧接指定字符串 n 的字符串。
3.2.4.7 RegExp 对象方法
1. test => 检索字符串中指定的值。返回 true 或 false。
let reg = /[a-z]/i;
reg.test('A') //true
reg.test('z') //true
reg.test(1) //false
2. exec => 检索字符串中指定的值。返回找到的值,并确定其位置。
let str = 'hello everybody!';
let reg = /e/i;
reg.exec(str) //["e", index: 6, input: "hello everybody!", groups: undefined]
reg.exec('abc') //null
3. compile => 编译正则表达式。用于改变 RegExp 对象
RegExpObject.compile(regexp,modifier)
let str = 'hello everybody!';
let reg = /e/i;
reg.compile('[0-9]', g)//reg: /[0-9]/g
3.2.4.8 支持正则表达式的 String 对象的方法
1. search => 检索与正则表达式相匹配的值(返回第一个匹配到的位置)
let str = 'hello everybody!';
let reg = /l/i;
str.search(reg) //2
reg = /f/g;
str.search(reg) //-1
2. match => 找到一个或多个正则表达式的匹配。(返回匹配到的值)
let str = 'hello everybody!';
let reg = /l/g;
str.match(reg) //["l", "l"]
reg = /f/g;
str.match(reg) //null
3. replace => 替换与正则表达式匹配的子串(返回替换后的整个字符串,改变原字符串)
let str = 'hello everybody!';
let reg1 = /l/;
str.replace(reg1, 'p') //"heplo everybody!"
str = 'hello everybody!';
let reg2 = /l/g;
str.replace(reg2, 'p') //"heppo everybody!"
4. split => 在符合 separator 的位置把字符串分割为字符串数组。
let str = 'hello everybody!';
let reg = /e/g;
str.split(reg) //["h", "llo ", "v", "rybody!"]
3.2.5 Number、String、Boolean 构造函数
前面 2.1 基本类型 章节已经介绍了 Number、String、Boolean 类型,下面介绍下 Number、String、Boolean 三种构造函数。
3.2.5.1 Number 构造函数
- 通过 Number 构造函数创建的是对象
let numObj = new Number(2);
let num = 2;
typeof numObj //"object"
typeof num //"number"
numObj === num; //false
//通过 Number 构造函数创建的是对象可以调用数字的方法
let sum = numObj + numObj; //4
let str = numObj + 'hello'; //"2hello"
//通过 Number 构造函数创建的是对象可以添加属性
numObj.name='jane'//
console.log(numObj.name) //"jane"
num.name='tom'
console.log(num.name) //undefined
- Number() 也可以把各种值转换为数字,不能转换的就是 NaN。
Number('1') //1
Number(true) //1
Number(false) //0
Number('text') //NaN
3.2.5.2 String 构造函数
- 通过 String 构造函数创建的是对象
let stringObj = new String('abc');
let string = 'abc';
typeof stringObj //"object"
typeof string //"string"
stringObj === string; //false
//通过 String 构造函数创建的是对象可以添加属性
stringObj.name='jane'//
console.log(stringObj.name) //"jane"
string.name='tom'
console.log(string.name) //undefined
//通过 String 构造函数创建的是对象可以调用字符串的方法
let testStr = new String('testStr');
testStr.substr(1,3) //"est" 截取 testStr 从第1个开始,取3个
testStr.substring(1,4) //"est" 截取 testStr 从第1个开始,到第4个,不包含第4个
testStr.slice(1,4) //"est" 截取 testStr 从第1个开始,到第4个,不包含第4个
testStr.substring(2,0) //"te"
testStr.slice(2,0) //""
- String() 也可以把各种值转换为字符串。
String(1) //"1"
String(true) //"true"
String(false) //"false"
String('text') //"text"
String([1,2,3]) //"1,2,3"
String({name: 'jane'}) //"[object Object]"
3.2.5.3 Boolean 构造函数*
通过 Boolean 构造函数创建的是对象
let booleanObj = new String(true);
let boolean = true;
typeof booleanObj //"object"
typeof boolean //"boolean"
booleanObj === boolean; //false
//通过 Boolean 构造函数创建的是对象可以添加属性
booleanObj.name='jane'//
console.log(booleanObj.name) //"jane"
boolean.name='tom'
console.log(boolean.name) //undefined
- Boolean() 也可以把各种值转换为布尔值,同 2.1 基本类型 里面介绍的一样 。
Boolean('111') === true
Boolean('text') === true//true
Boolean(2) === true//true
Boolean({}) === true//true
Boolean({name: 'jane'}) === true//true
3.2.6 Math
Math 不是对象的类,没有构造函数,但作为 JavaScript 内置的对象,可直接调用来进行数学方法的处理。
let num = 12345.12;
//四舍五入
Math.round(num); //12345
//下舍入
Math.floor(num); //12345
//上舍入
Math.ceil(num); //12346
//取两个数中的最高值
Math.max(2,3) //3
//取两个数中的最低值
Math.max(2,3) //2
//返回 0 ~ 1 之间的随机数
Math.random() //0.657487039270241
3.3 原型
所有引用类型其隐式原型都指向它的构造函数的显式原型
obj.__proto__ === Object.prototype
- 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,会去它的 __proto__ (即其构造函数的 prototype )中寻找。
隐式原型:
所有引用类型(数组、对象、函数),都有一个 __proto__ 属性,属性值是一个普通对象。
显式原型:
所有的函数都有一个 prototype 属性,属性值是一个普通对象。
hasOwnProperty:
只判断对象本身是否包含某属性,不去其原型链中寻找。
3.4 原型链
当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,会去它的 __proto__ (即其构造函数的 prototype )中寻找,这在编程中被称为“原型继承”。
3.5 判断数据类型(构造函数、原型、typeof)
3.5.1 typeof
typeof——返回给定变量的数据类型,可能返回如下字符串:
返回字符串 —— 数据类型
'undefined' —— Undefined
'boolean' —— Boolean
'string' —— String
'number' —— Number
'symbol' —— Symbol
'object' —— Object / Null (Null 为空对象的引用)
'function' —— Function
- typeof 返回的类型都是字符串形式,需注意,例如:
alert(typeof 'abcd' === "string") => true
alert(typeof 'abcd' === String) => false
- typeof 对于Array、RegExp、Date 类型统一返回 'object'
alert(typeof [] === 'object') => true
alert(typeof new Date) => 'object'
- typeof 对于 function 类型统一返回 'function'
alert(typeof function(){} === 'function') => true
- typeof 是操作符而非函数,所以可以使用圆括号,也可以不使用。
alert(typeof 'abcd'); / alert(typeof ('abcd'));
3.5.2 instanceof
判断一个函数是否是变量的构造函数
语法: objectA instanceof constructorB
判断逻辑: 变量a的 _ proto _ 一层一层往上找,用来检测 constructor.prototype 是否存在于参数 object 的原型链上,是则返回 true,不是则返回 false。
alert([1,2,3] instanceof Array) ---------------> true
alert(new Date() instanceof Date) ------------> true
alert(function(){this.name="22";} instanceof Function) ------------> true
alert(function(){this.name="22";} instanceof function) ------------> false
3.5.3 constructor
返回对象对应的构造函数
alert(({}).constructor === Object); => true
alert(([]).constructor === Array); => true
alert('abcde'.constructor === String); => true
alert((1).constructor === Number); => true
alert(true.constructor === Boolean); => true
alert(false.constructor === Boolean); => true
alert((function s(){}).constructor === Function); => true
alert(new Date().constructor === Date); => true
alert(new Array().constructor === Array); => true
alert(new Error().constructor === Error); => true
alert(document.constructor === HTMLDocument); => true
alert(window.constructor === Window); => true
alert(Symbol().constructor === Symbol); => true
null 和 undefined 是无效的对象,没有 constructor,因此无法通过这种方式来判断。
注:
- constructor 不能用来判断 Null 及 Undefined 类型
- 函数的 constructor 不稳定。
当一个函数被定义时,JS 引擎会为其添加 prototype 原型,然后在 prototype 上添加一个 constructor 属性,并让其指向函数的引用。
但函数的 prototype 被重写后,原有的 constructor 引用会丢失。再次新建一个次函数的实例后,其 constructor 指向的内容已发生改变。
因此为了规范开发,在重写对象原型时,一般都需要重新给 constructor 赋值,以保证对象实例的类型不被更改。
3.5.4 Object.prototype.toString()
- toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。
- 这是一个内部属性,其格式为 [object Xxx] ,是一个字符串,其中 Xxx 就是对象的类型。
- 对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。
Object.prototype.toString.call(new Date) === '[object Date]'; //true
Object.prototype.toString.call(new String) === '[object String]'; //true
Object.prototype.toString.call(Math) === '[object Math]'; //true
Object.prototype.toString.call(undefined) === '[object Undefined]'; //true
Object.prototype.toString.call(null) ==='[object Null]'; //true
Object.prototype.toString.call('') === '[object String]' ; //true
Object.prototype.toString.call(123) === '[object Number]' ; //true
Object.prototype.toString.call(true) === '[object Boolean]' ; //true
Object.prototype.toString.call(Symbol()) === '[object Symbol]'; //true
Object.prototype.toString.call(new Function()) === '[object Function]'; //true
Object.prototype.toString.call(new Date()) === '[object Date]' ; //true
Object.prototype.toString.call([]) === '[object Array]'; //true
Object.prototype.toString.call(new RegExp()) === '[object RegExp]' ; //true
Object.prototype.toString.call(new Error()) === '[object Error]'; //true
Object.prototype.toString.call(document) === '[object HTMLDocument]'; //true
Object.prototype.toString.call(window) === '[object Window]'; //true
类型判断小结:
- 1)typeof 更适合判断基本类型数据,因为对于引用类型数据,typeof 只会返回 ‘function’ 或 ‘object’,不会返回其他的数组等类型;
- 2)instanceof 只能用来判断实例类型,包括 Array、Date 等;
- 3)constructor 不能用来判断 Null 及 Undefined 类型
- 4)注:new String()、new Number() 生成的实际上为对象,但只能通过 typeof 能判断出来,constructor 和 .toString 只会返回 String 或 Number,无法判断是基本类型或是引用类型。
3.6 模拟面向对象的类
面向对象的编程中的类: 是用于创建对象的可扩展的程序代码模版,它为对象提供了状态(成员变量)的初始值和行为(成员函数和方法)的实现。
ES6 中封装了 class,在 ES6 中的 class 出现前,也可以通过其他的编程模式来创建类。
3.6.1 基于构造函数的类
function Person(name, age) {
function getAge() {
return age + 10;
}
this.outputInfo = function() {
console.log('name:', name, 'currentAge: ', getAge())
}
}
let jane = new Person('jane', 14);
jane.outputInfo() //name: jane currentAge: 24
优点:
如上,getAge
方法是外部不可见的(私有的),可隐藏内部方法的实现;而outputInfo
被分配到this
上,则是外部可见的(即公有的),用此构造函数创建的对象可以使用共有方法。缺点:
所有的方法都在构造函数内部,每次 new 一个实例对象,都会创建内部的这些方法,且不同实例对象间不能共享这些方法,浪费内存资源。
3.6.2 基于原型的类
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype._getAge = function() {
return this.age + 10;
}
Person.prototype.outputInfo = function() {
console.log('name:', this.name, 'currentAge: ', this._getAge())
}
let jane = new Person('jane', 14);
jane.outputInfo() //name: jane currentAge: 24
- 优点:
如上,将公共方法放在原型中,创建新对象直接调用原型中的方法,对象中没有的属性和方法,可以在原型链中寻找。
3.7 ES6 中的 class
在 JavaScript 中,class 实际上是一种“语法糖”,我们可以使用 class 去干净整洁地定义基于原型的类。
3.7.1 class 使用
3.7.1.1 基于原型实现类
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype._getAge = function() {
return this.age + 10;
}
Person.prototype.outputInfo = function() {
console.log('name:', this.name, 'currentAge: ', this._getAge())
}
let jane = new Person('jane', 14);
jane.outputInfo() //name: jane currentAge: 24
3.7.1.2 基于 class 实现相同的类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
getAge () {
return this.age + 10;
}
outputInfo () {
console.log('name:', this.name, 'currentAge: ', this.getAge())
}
}
let jane = new Person('jane', 14);
jane.outputInfo() //name: jane currentAge: 24
3.7.2 class 完成了如下过程:
- 声明了
Person
变量,且将它的值指向constructor
函数,如下图:
- 所有类中定义的方法挂到了
Person.prototype
上,如下图:
3.7.3 class 特点:
- 必须与
new
一同使用,如下图:
class 拥有一个默认的
constructor(){}
,
class 中没有定义constructor
的话,会默认生成一个空的构造函数:constructor(){}
。class 中的方法都是不可枚举的
这样遍历新建的对象时,类上的方法不会被遍历。class 中只能定义方法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
subName: 'jane'//只能定义方法
}
//Uncaught SyntaxError: Unexpected identifier
//如果一定要添加可在原型链上添加或通过 getter 添加
3.7.4 静态方法
新增在 class 上而非新增在 prototype
上的方法,称为静态方法。
3.7.4.1 定义静态方法
class Person {
static sum(a, b){
console.log(a+b)
}
}
3.7.4.2 静态方法调用
静态方法需直接通过该类来调用,无需实例化,实例化调用会报错
class Person {
static sum(a, b){
console.log(a+b)
}
}
Person.sum(1, 2);//3
let person = new Person();
person.sum(1, 2);//报错:Uncaught TypeError: person.sum is not a function
3.7.5 类继承
3.7.5.1 类继承的实现
假设现在有两个类:Person 和 Student
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
getAge () {
return this.age + 10;
}
outputInfo () {
console.log('name:', this.name, 'currentAge: ', this.getAge())
}
}
class Student {
constructor(name, age) {
this.name = name;
this.age = age;
}
getAge () {
return this.age + 10;
}
outputInfo () {
console.log('name:', this.name, 'currentAge: ', this.getAge())
}
startStudy () {
alert(`我是${this.name}, 我要学习啦!`)
}
}
let student = new Student('jane', 12)
student.startStudy()
如上,两个类是独立的,但其实 Student 是属于 Person 的,Person 中的方法 Student 中都有,且可以自己进行拓展,所以我们可以使用 “extends” 让 Student 来“继承” Person:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
getAge () {
return this.age + 10;
}
outputInfo () {
console.log('name:', this.name, 'currentAge: ', this.getAge())
}
}
class Student extends Person {
startStudy () {
alert(`我是${this.name}, 我要学习啦!`)
}
}
let student = new Student('jane', 12)
student.startStudy()
如上,便实现了 Student 对 Person 的继承,如果在 Student.prototype 中没有找到某个方法,就会从 Person.prototype 中继续寻找。
3.7.5.2 super 的作用
1. 重写构造函数 —— 继承父类参数
当子类有构造函数时,必须调用 super 方法来继承父类的参数。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
getAge () {
return this.age + 10;
}
outputInfo () {
console.log('name:', this.name, 'currentAge: ', this.getAge())
}
}
class Student extends Person {
constructor(name, age) {
super(name, age); //继承父类参数,必须写在构造函数最前面
this.studyStatus = false; //必须写在 super() 之后,否则报错
}
startStudy () {
super.outputInfo()
alert(`我是${this.name}, 我要学习啦!`)
}
}
let student = new Student('jane', 12)
student.startStudy()
2. 调用父类的方法 —— super.fun()
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
getAge () {
return this.age + 10;
}
outputInfo () {
console.log('name:', this.name, 'currentAge: ', this.getAge())
}
}
class Student extends Person {
constructor(name, age) {
super(name, age); //继承父类参数,必须写在构造函数最前面
this.studyStatus = false; //必须写在 super() 之后,否则报错
}
startStudy () {
super.outputInfo() //会执行父类的这个方法
alert(`我是${this.name}, 我要学习啦!`)
}
}
let student = new Student('jane', 12)
student.startStudy()
箭头函数没有自己的 super,它们的 super 即是就近的上下文的 super,同它的 this 一样。
3.7.5.3 重写父类方法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
getAge () {
return this.age + 10;
}
outputInfo () {
console.log('name:', this.name, 'currentAge: ', this.getAge())
}
}
class Student extends Person {
constructor(name, age) {
super(name, age); //继承父类参数,必须写在构造函数最前面
this.studyStatus = false; //必须写在 super() 之后,否则报错
}
outputInfo () {
alert(`name: ${this.name}, currentAge: ${this.getAge()}`)
}
}
let student = new Student('jane', 12)
student.outputInfo()
如上,子类中有与父类相同的方法,会直接覆盖父类的方法,因为执行时,会现在 Student.prototype 中寻找这个方法,没有找到时,才会从 Person.prototype 中继续寻找。
参考:现代 JavaScript 教程