1、标识符
命名规则
- 第一个字符必须是一个字母、下划线( _ )或一个美元符号( $ )。
- 其它字符可以是字母、下划线、美元符号或数字。
- 按照惯例,ECMAScript 标识符采用驼峰命名法。
- 标识符不能是关键字和保留字符。
2、字面量和变量
2.1、字面量
字面量实际上就是一些固定的值,比如:1、2 、3、true、false、null、NaN、“hello”,字面量都是不可以改变的,由于字面量不是很方便使用,所以在JavaScript中很少直接使用字面量,使用的而是变量。可以理解成Java中的常量。
2.2、变量
变量的声明
var a;
变量的赋值
a=0;
声明和赋值同时进行
var num=0;
var str="My name is HelloWorld";
变量提升
JavaScript 在预编译期会先预处理声明的变量,但是变量的赋值操作发生在 JavaScript 执行期,而不是预编译期。
document.write(str);//显示undefined
document.write("<br>");
str="变量提升";
document.write(str);//显示变量提升
var str;
JavaScript 引擎的解析方式是:先解析代码,获取所有被声明的变量,然后再一行一行地运行。 这样,所有声明的变量都会被提升到代码的头部,这就叫作变量提升(Hoisting)。
let 和 const 关键字
在ES6发布(2015年)以前,JavaScript只能用var进行变量声明,ES6发布之后,增加了let和const两个关键字用来声明变量
使用let关键字声明的变量只在其代码块中生效,并且在其代码块中不能有同名的变量。类似于C#的局部变量。
const 关键字的功能和 let 相同,但是有多了一个功能,就是使用const关键字声明的变量一旦被定义,就不能被修改(即使用const定义的是常量)
IE10 及以下的版本不支持 let 和 const 关键字
let a = "皇帝"; // 声明一个变量 a 并赋值为“皇帝”
let b = 11; // 声明一个变量 bb
//let b = 13; // 报错:变量 b 不能重复定义
const PI = 3.1415; // 声明一个常量 PI,并赋值为 3.1415
console.log(PI); // 在控制台打印 PI
PI=3.14156; // 报错:Assignment to constant variable.
console.log(PI);
3、数据类型
JavaScript共有2种数据类型即基本数据类型+引用类型
typeof运算符
typeof操作符可以用来检查一个变量的数据类型。
typeof 数据
var object = {name:"钢铁侠"};
console.log(typeof 123);//number
console.log(typeof "钢铁侠");// string
console.log(typeof true);//boolean
console.log(typeof undefined);// undefined
console.log(typeof null);// object
console.log(typeof object);// object
console.log(typeof Symbol(1));// symbol
3.1、基本数据类型
3.1.1 字符串型(string)
符串(String)类型是一段以单引号''
或双引号""
包裹起来的文本。如果字符串中包含引号,可以使用反斜杠 \
来转义字符串中的引号,或者选择与字符串中不同的引号来定义字符串。
var str0="我是一个字符串";
var str1="我是一个'string'字符串";
var str2='我是一个"string"字符串';
var str3='我是一个\'string\'字符串';
var str4="我是一个\"string\"字符串";
console.log(str0);
console.log(str1);
console.log(str2);
console.log(str3);
console.log(str4);
console.log(typeof(str0));
3.1.2 数值型(number)
数值(Number)类型用来定义数值,JavaScript 中不区分整数和小数(浮点数),统一使用 Number 类型表示。
var a=100;
var b=3.1415;
console.log(typeof(a));// number
console.log(typeof(b));// numbe
Number 类型所能定义的数值并不是无限的,JavaScript 中的 Number 类型只能表示 -(2的53次方 - 1) 到 (2的53次方 -1) 之间的数值。
对于一些极大或者极小的数,也可以通过科学(指数)计数法来表示
var a=520e5;//52000000
var b=520e-5;//0.0052
console.log(a);
console.log(b);
Number 类型中还有几个比较特殊的值
var a=Infinity;//无穷大
var b=-Infinity;//无穷小
var c=NaN;//不是数字
console.log(a);
console.log(b);
console.log(c);
//如果某次计算的结果超出了 JavaScript 中 Number 类型的取值范围,那么这个数就会自动转化为无穷大,正数为 Infinity,负数为 -Infinity。
3.1.3 布尔型(Boolean)
布尔(Boolean)类型只有两个值,true(真)或者 false(假)
var a=true;// true
var b=false;// false
var c=8>5;// true
var d=8<5;// false
console.log(a);
console.log(b);
console.log(c);
console.log(d);
console.log(typeof(a))
3.1.4 undefined型(undefined)
Undefined 也是一个只有一个值的特殊数据类型,表示未定义。当我们声明一个变量但未给变量赋值时,这个变量的默认值就是 Undefined。
var a;
console.log(a); //undefined
console.log(typeof(a));//undefined
3.1.5 null型(null)
Null 是一个只有一个值的特殊数据类型,表示一个“空”值,即不存在任何值,什么都没有,用来定义空对象指针。
使用 typeof 操作符来查看 Null 的类型,会发现 Null 的类型为 Object,说明 Null 其实使用属于 Object(对象)的一个特殊值。因此通过将变量赋值为 Null 我们可以创建一个空的对象。
var a=null;
console.log(a); //null
console.log(typeof(a));//object
3.1.6 Symbol 类型
Symbol 是 ECMAScript6 中引入的一种新的数据类型,表示独一无二的值,Symbol 类型的值需要使用 Symbol() 函数来生成。
var str = "123";
var sym1 = Symbol(str);
var sym2 = Symbol(str);
console.log(sym1); // Symbol(123)
console.log(sym2); // Symbol(123)
console.log(sym1 == sym2); // false
console.log(typeof(sym1)); // symbol
3.2、引用数据类型
3.2.1 Object类型
JavaScript 中的对象(Object)类型是一组由键、值组成的无序集合,定义对象类型需要使用花括号{ }
。
var person = {name: '皇帝',age: 20,city: '长安',hasCar: true,zipcode: null
};
console.log(person.name); // 皇帝
console.log(person.age); // 20
3.2.2 Array 类型
数组(Array)是一组按顺序排列的数据的集合,数组中的每个值都称为元素,而且数组中可以包含任意类型的数据。在 JavaScript 中定义数组需要使用方括号[ ]
,数组中的每个元素使用逗号进行分隔。
var arr=[1,2,'三','四','五'];
console.log(arr);//1,2,'三','四','五'
console.log(arr[0]);//1
console.log(arr[6]);//undefined 因为索引超出了范围
3.2.3 Function 类型
函数(Function)是一段具有特定功能的代码块,函数并不会自动运行,需要通过函数名调用才能运行。
function add(a, b) {return a + b;
}
var sum = add(10, 20);//10,20是参数
console.log(sum);//30
4、强制类型转换
4.1、转换为String类型
三种方式:toString()、String()、拼接
方式一:调用被转换数据类型的toString()方法
该方法不会影响到原变量,会将转换的结果返回。null和undefined这两个值没有toString()方法,如果调用,会报错。
var a = 1234;
console.log(typeof a);// number
var a1 = a.toString();
console.log(a);// 1234
console.log(typeof a1);// string
console.log(typeof a);// number
方式二:调用String()函数,并将被转换的数据作为参数传递给函数。
使用String()函数做强制类型转换时,对于Number和Boolean实际上就是调用的toString()方法,但是对于null和undefined,就不会调用toString()方法,它会将 null 直接转换为 “null”,将 undefined 直接转换为 “undefined”。
var b = 54321;
var b1 = String(b);
console.log(b1);// 54321
console.log(typeof b1);// string
console.log(typeof b);// numbervar c = undefined;
var c1 = String(c);
console.log(c1);// undefined
console.log(typeof c1);// string
console.log(typeof c);// undefinedvar d = null;
var d1 = String(d);
console.log(d1);// null
console.log(typeof d1);// string
方式三:为任意的数据类型 +""
var s = 123;
var s1 = "木头人";
var s2 = s + s1;
console.log(s2);// 123木头人
console.log(typeof s2);// string
4.2、转换为Number类型
方式一:使用Number()函数
1.字符串 --> 数字
如果是纯数字的字符串,则直接将其转换为数字
如果字符串中有非数字的内容,则转换为NaN
如果字符串是一个空串或者是一个全是空格的字符串,则转换为0
var str = "123";
var str1 = "a1";
var str2 = " ";
console.log(Number(str));//123
console.log(Number(str1));//NaN
console.log(Number(str2));//0
2.布尔 --> 数字
true 转成 1
false 转成 0
var b1=true;
var b2=false;
console.log(Number(b1));//1
console.log(Number(b2));//0
3.null --> 数字
null 转成 0
var c=null;
console.log(Number(c));//0
4.undefined --> 数字
undefined 转成 NaN
var c=undefined;
console.log(Number(c));//NaN
方式二:parseInt() 把一个字符串转换为一个整数
var c="123.456";
console.log(parseInt(c));//123
方式三:parseFloat() 把一个字符串转换为一个浮点数
var c="123.456";
console.log(parseFloat(c));//123.456
注意:如果对非String使用parseInt()或parseFloat(),它会先将其转换为String然后在操作
4.3、转换为Boolean类型
将其它的数据类型转换为Boolean,只能使用Boolean()函数。
数字 —> 布尔
除了0和NaN,其余的都是true
var c=123;
var m=0;
var n=NaN;
console.log(Boolean(c));//true
console.log(Boolean(m));//false
console.log(Boolean(n));//false
字符串 —> 布尔
除了空串,其余的都是true
null和undefined都会转换为false,对象也会转换为true
var c="123";
var d=" ";
var m="";
var n=null;
var i=undefined;
var obj={a:"123"};
console.log(Boolean(c));//true
console.log(Boolean(d));//true
console.log(Boolean(m));//false
console.log(Boolean(n));//false
console.log(Boolean(i));//false
console.log(Boolean(obj))//true
5、运算符
5.1、算术运算符
当a=3
运算符 | 描述 | 例子 | b结果 | a结果 |
---|---|---|---|---|
+ | 加法 | b=a+1 | 4 | 3 |
- | 减法 | b=a-1 | 2 | 3 |
* | 乘法 | b=a*2 | 6 | 3 |
/ | 除法 | b=a/2 | 1 | 3 |
% | 取余 | b=a%2 | 1 | 3 |
++ | 自增 | b=++a,b=a++ | 4,3 | 4,4 |
– | 自减 | b=–a,b=a– | 2,3 | 2,2 |
5.2、关系运算符
当x=3
运算符 | 描述 | 比较 | 返回值 |
---|---|---|---|
> | 大于 | x>5 | false |
< | 小于 | x<5 | true |
>= | 大于等于 | x>=5 | false |
<= | 小于等于 | x<=5 | true |
5.3、赋值运算符
当x=6,y=3
运算符 | 例子 | 等同于 | 运算结果 |
---|---|---|---|
= | x=y | x=6 | |
+= | x+=y | x=x+y | x=9 |
-= | x-=y | x=x-y | x=3 |
*= | x*=y | x=x*y | x=18 |
/= | x/=y | x=x/y | x=2 |
%= | x%=y | x=x%y | x=0 |
5.4、逻辑运算符
当x=6,y=3
运算符 | 描述 | 例子 |
---|---|---|
&& | and | (x<10&&y>0)是true |
|| | or | (x5||y5)是false |
! | not | !(x==y)是true |
5.5、比较运算符
当x=5,y=“5”
运算符 | 例子 | 结果 |
---|---|---|
== | x==y | true |
!= | x!=y | false |
=== | x===y | false |
!== | x!==y | true |
5.6、条件运算符(三元运算符)
语法:variablename=(condition)?value1:value2;
举例:result=(age<18)?"年龄太小":"年龄合适";
var age=15;
var result=(age<18)?"年龄太小":"年龄合适";
console.log(result);//年龄太小
5.7、逗号运算符
使用逗号运算符可以在一个语句中执行多次操作
var a1=1,a2=2,a3=3;
console.log(a1);//1
console.log(a2);//2
console.log(a3);//3
5.8、位运算符
var a = 5 & 1,b = 5 | 1,c = 5 ^ 1,d = ~ 5,e = 5 << 1,f = 5 >> 1,g = 5 >>> 1;
console.log(a); // 输出:1
console.log(b); // 输出:5
console.log(c); // 输出:4
console.log(d); // 输出:-6
console.log(e); // 输出:10
console.log(f); // 输出:2
console.log(g); // 输出:2
6、运算符优先级
7、代码块
7.1、语句
每一条语句使用;结尾。
7.2、代码块
代码块是在大括号 {} 中所写的语句,以此将多条语句的集合视为一条语句来使用。
8、条件语句
8.1、if…else
//第一种if
var age = 16;
if (age < 18) {console.log("未成年");
}
//第二种if else
var age = 16;
if (age < 18) {console.log("未成年");
} else {console.log("已成年");
}
//第三种if elseif else
var age = 18;
if (age < 18) {console.log("小于18岁了");
} else if (age == 18) {console.log("已经18岁了");
} else {console.log("大于18岁了")
}
8.2、switch…case
//第一例
var today = 5;switch (today) {case 0:console.log("今天是星期日。");break;case 1:console.log("今天是星期一。");break;case 2:console.log("今天是星期二。");break;case 3:console.log("今天是星期三。");break;case 4:console.log("今天是星期四。");break;case 5:console.log("今天是星期五。");break;case 6:console.log("今天是星期六。");break;default:console.log("输入错误!!!");}
//第二例
var month = 5;switch (month) {case 1:case 3:case 5:case 7:case 8:case 10:case 12:console.log("这个月有31天");break;case 4:case 6:case 9:case 11:console.log("这个月有30天");break;case 2:console.log("这个月有28天");break;default:console.log("输入错误!!!");}
9、循环语句
9.1、whlie
var x = 1;while (x < 5) {console.log("这是第" + x + "次循环");x++;}
9.2、do…while
var x = 1; do{console.log("这是第" + x + "次循环");x++;}while(x<5)
9.3、for
for(i=0;i<5;i++){console.log("这是第"+i+"次循环");}
for (var i = 1; i <= 9; i++) {for (var j = 1; j <= i; j++) {document.write(j + " x " + i + " = " + (i * j) + " ");}document.write("<br>");
}
9.4、for循环变体
9.4.1、for in循环
for in 循环是一种特殊类型的循环,也是普通 for 循环的变体,主要用来遍历对象,使用它可以将对象中的属性依次循环出来。
var person={name:"张三",age:18,sex:"男",say:function(){console.log("你好");}
}
for (var prop in person){document.write("<p>" + prop + " = " + person[prop] + "</p>");
}
9.4.2、for of 循环
for of 循环是 ECMAScript6 中新添加的一个循环方式,与 for in 循环类似,也是普通 for 循环的一种变体。使用 for of 循环可以轻松的遍历数组或者其它可遍历的对象。虽然 for of 循环也可以遍历对象,但并不推荐,若要遍历对象可以使用 for in 循环。
var arr=["张三","李四","王五"];
for (var prop of arr){document.write("<p>" + prop + "</p>");
}
var str="我是一个字符串";
for (var prop of str){document.write("<p>" + prop + "</p>");
}
9.5、跳转控制
- break:结束最近的一次循环,可以在循环和switch语句中使用。
- continue:结束本次循环,执行下一次循环,只能在循环中使用。
9.6、JavaScript标签
JavaScript中的“标签”通常与循环结构(如for
、while
)或switch
语句一起使用,以便更精确地控制程序的流程。标签提供了从内层循环直接跳转到外层循环或其他代码位置的能力,这在某些复杂的控制流程中非常有用。
当使用break
或continue
语句时,可以指定一个标签来影响特定层级的循环。
使用标签和break
跳出外层循环
outerLoop: for (let i = 0; i < 5; i++) {for (let j = 0; j < 5; j++) {if (i === 2 && j === 2) {break outerLoop; // 当i=2且j=2时,跳出outerLoop循环}console.log(`i=${i}, j=${j}`);}
}
使用标签和continue
跳过某次循环
outerLoop: for (let i = 0; i < 5; i++) {for (let j = 0; j < 5; j++) {if (i === 2 && j === 2) {continue outerLoop; // 当i=2且j=2时,跳过outerLoop循环的当前迭代}console.log(`i=${i}, j=${j}`);}
}
10、对象
10.1、对象的概述
Object类型,我们也称为一个对象,是JavaScript中的引用数据类型。它是一种复合值,它将很多值聚合到一起,可以通过名字访问这些值。对象也可以看做是属性的无序集合,属性是一个个键:值
对的组合,其中键(属性名称)始终是字符串类型的,而值(属性值)则可以是任意类型,对象除了可以创建自有属性,还可以通过从一个名为原型的对象那里继承属性。除了字符串、数字、true、false、null和undefined之外,JavaScript中的值都是对象
10.2、创建对象
//第一种方法
var people=new Object();
people.name="轩辕";
people.age=5000;
console.log(people);
//第二种方法
var people1={"people name":"神农",age:5000,sex:"男"
}
console.log(people1);
10.3、访问属性
在访问对象属性时,使用 对象名.属性名
的形式更易于代码的编写,但并不是所有情况下都可以使用。如果属性名中包含空格或者特殊字符,则不能使用 对象名.属性名
的形式来访问对象属性,必须使用 对象名["属性名"]
的形式才行。
//第一种方法
console.log(people.name);
//第二种方法
console.log(people1["people name"]);
10.4、删除属性
delete people1.age;
console.log(people1);
10.5、遍历对象
var people1={name:"神农",age:5000,sex:"男"
}
for(var p in people1){var s=people1[p];console.log(p+":"+s)
}
10.6、数据类型梳理
JavaScript中的变量可能包含两种不同数据类型的值:基本数据类型和引用数据类型。
10.6.1、基本数据类型
JavaScript中一共有6种基本数据类型:String、Number、 Boolean、Undefined、Null、Symybol。
基本数据类型的值是无法修改的,是不可变的。
基本数据类型的比较是值的比较,也就是只要两个变量的值相等,我们就认为这两个变量相等。
10.6.2、引用数据类型
引用类型的值是保存在内存中的对象。
当一个变量是一个对象时,实际上变量中保存的并不是对象本身,而是对象的引用。
当从一个变量向另一个变量复制引用类型的值时,会将对象的引用复制到变量中,并不是创建一个新的对象。
这时,两个变量指向的是同一个对象。因此,改变其中一个变量会影响另一个。
10.7、栈和堆梳理
JavaScript在运行时数据是保存到栈内存和堆内存当中的。
简单来说栈内存用来保存变量和基本类型,堆内存是用来保存对象。
我们在声明一个变量时,实际上就是在栈内存中创建了一个空间用来保存变量。
如果是基本类型则在栈内存中直接保存,如果是引用类型则会在堆内存中保存,变量中保存的实际上对象在堆内存中的地址。
栈的特点:先进后出,后进先出
11、函数
11.1、概述
函数是由一连串的子程序(语句的集合)所组成的,可以被外部程序调用,向函数传递参数之后,函数可以返回一定的值。通常情况下,JavaScript代码是自上而下执行的,不过函数体内部的代码则不是这样。如果只是对函数进行了声明,其中的代码并不会执行,只有在调用函数时才会执行函数体内部的代码。要注意的是JavaScript中的函数也是一个对象,使用typeof检查一个函数对象时,会返回function。
11.2、函数创建
//使用 函数对象 来创建一个函数(几乎不用)
var fun1 = new Function("console.log('我是用函数对象来创建函数');");
//使用 函数声明 来创建一个函数(比较常用)
function fun2() {console.log("我是函数声明来创建一个函数");
}
//使用 函数表达式 来创建一个函数(比较常用)
var fun3 = function (a, b) {console.log("我是函数表达式来创建一个函数");console.log(a + b);
}
11.3、函数调用
fun1();
fun2();
fun3(5,5);
11.4、函数参数
JS中的所有的参数传递都是按值传递的,也就是说把函数外部的值赋值给函数内部的参数,就和把值从一个变量赋值给另一个变量是一样的,在调用函数时,可以在()中指定实参(实际参数),实参将会赋值给函数中对应的形参
调用函数时,解析器不会检查实参的类型,所以要注意,是否有可能会接收到非法的参数,如果有可能,则需要对参数进行类型的检查,函数的实参可以是任意的数据类型
调用函数时,解析器也不会检查实参的数量,多余实参不会被赋值,如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined
11.5、函数返回值
可以使用 return 来设置函数的返回值,return后的值将会作为函数的执行结果返回,可以定义一个变量,来接收该结果。
var fun3=function(a,b){console.log("我是函数表达式来创建一个函数");return a+b+10;
}
console.log(fun3(5,5));
11.6、嵌套函数
嵌套函数:在函数中声明的函数就是嵌套函数,嵌套函数只能在当前函数中可以访问,在当前函数外无法访问。
var fun=function(){var fun1=function(){console.log("我是一个嵌套函数")}fun1();
}
fun();
fun1();//会报错
11.7、匿名函数
匿名函数:没有名字的函数就是匿名函数,它可以让一个变量来接收,也就是用 “函数表达式” 方式创建和接收。
var fun = function () {console.log("我是一个匿名函数")
}
fun();
11.8、立即执行函数
立即执行函数:函数定义完,立即被调用,这种函数叫做立即执行函数,立即执行函数往往只会执行一次。
(function () {alert("我是一个立即执行函数");
})();
11.9、对象中的函数
对象的属性值可以是任何的数据类型,也可以是个函数。
如果一个函数作为一个对象的属性保存,那么我们称这个函数是这个对象的方法,调用这个函数就说调用对象的方法(method)。
var car={name:"玛莎拉蒂",age:1,start:function(){console.log("轰隆隆");}
}
car.start();
11.10、this对象
解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this,this指向的是一个对象,这个对象我们称为函数执行的上下文对象,根据函数的调用方式的不同,this会指向不同的对象
以函数的形式调用时,this永远都是window
以方法的形式调用时,this就是调用方法的那个对象
var name="我是全局";
var fun=function(){console.log(this.name);//函数的形式调用时,this永远都是window
}
fun();
var student={name:"孔子",sayName:fun//对象的形式调用时,this指向的是对象本身
}
student.sayName();
12、对象进阶
12.1、用工厂方法创建对象
//使用工厂模式创建对象function creatCar(name, pirce) {var car = new Object();car.name = name;car.pirce = pirce;car.lookCar = function () {console.log(this.name + ":" + this.pirce);}return car;}var car1 = creatCar("凯迪拉克", 200);var car2 = creatCar("特斯拉", 500);console.log(car1);console.log(car2);for (i = 0; i < 10; i++) {var car = creatCar("小汽车" + i, i * 100);console.log(car);}
12.2、用构造函数创建对象
构造函数:构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写,构造函数和普通函数的还有一个区别就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用。
构造函数如何执行创建对象的过程
- 调用构造函数,它会立刻创建一个新的对象
- 将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
- 逐行执行函数中的代码
- 将新建的对象作为返回值返回
function MyCar(name, value) {this.name = name;this.value = value;this.drive = function () {console.log(this.name);}}var mycar1=new MyCar("特斯拉",5);var mycar2=new MyCar("奥迪",7);console.log(mycar1);console.log(mycar2);
当以构造函数的形式调用时,this就是新创建的那个对象
使用 instanceof 运算符检查一个对象是否是一个类的实例
console.log(mycar1 instanceof MyCar);
12.3、原型
我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype,这个属性对应着一个对象,这个对象就是我们所谓的原型对象,即显式原型,原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。
如果函数作为普通函数调用prototype没有任何作用,当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过__proto__(隐式原型)来访问该属性。当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。
以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了。
function Car(name,value){this.name=name;this.value=value;}Car.prototype.drive=function(){console.log(this.name);}var car1=new Car("宝马",1);var car2=new Car("奥迪",2);car1.drive();car2.drive();
12.4、原型链
访问一个对象的属性时,先在自身属性中查找,找到返回, 如果没有,再沿着__proto__这条链向上查找,找到返回,如果最终没找到,返回undefined,这就是原型链,又称隐式原型链,它的作用就是查找对象的属性(方法)。
注意:Object对象是所有对象的祖宗,Object的原型对象指向为null,也就是没有原型对象
12.5、toString方法
类型 | 行为描述 |
---|---|
String | 返回 String 对象的值。 |
Number | 返回 Number 的字符串表示。 |
Boolean | 如果布尔值是true,则返回"true"。否则返回"false"。 |
Object (默认) | 返回"[object ObjectName]",其中 ObjectName 是对象类型的名称。 |
Array | 将 Array 的每个元素转换为字符串,并将它们依次连接起来,两个元素之间用英文逗号作为分隔符进行拼接。 |
Date | 返回日期的文本表示。 |
Error | 返回一个包含相关错误信息的字符串。 |
Function | 返回如下格式的字符串,其中 functionname 是一个函数的名称 此函数的 toString 方法被调用: “function functionname() { [native code] }” |
12.6、hasOwnProperty方法
// 创造一个构造函数
function MyClass() {
}// 向MyClass的原型中添加一个name属性
MyClass.prototype.name = "我是原型中的名字";// 创建一个MyClass的实例
var mc = new MyClass();
mc.age = 18;// 使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
console.log("age" in mc);
console.log("name" in mc);// 可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性,使用该方法只有当对象自身中含有属性时,才会返回true
console.log(mc.hasOwnProperty("age"));
console.log(mc.hasOwnProperty("name"));
12.7、对象继承
JavaScript中没有类的概念,JavaScript语言是通过一种叫做原型(prototype)的方式来实现面向对象编程的。
六种非常经典的对象继承方式
12.7.1、原型链继承
原型链继承是一种基于原型对象的继承方式。每个 JavaScript 对象都有一个指向其原型的内部链接。当试图访问一个对象的属性时,如果对象本身没有这个属性,那么 JavaScript 会在对象的原型(也就是它的__proto__
属性)上查找这个属性,如果还没有,就会继续查找原型的原型,直到找到属性或者达到原型链的顶端(null
)。
// 定义一个基础对象
var animal = {name: 'animal',speak: function () {console.log("我是"+this.name);}
};// 定义一个继承自 animal 的对象
var dog = {breed:"拉布拉多"
};
// 设置 dog 的原型为 animal,实现继承
dog.__proto__ = animal;
// 修改 dog 的 name 属性
dog.name = "豆花";
// 调用继承的方法
dog.speak();// 我是豆花
console.log(dog.breed);// 拉布拉多
console.log(dog.name);// 豆花
12.7.2、借用构造函数继承
借用构造函数继承(也称为经典继承或构造函数继承)是一种通过在子类构造函数内部调用父类构造函数来实现继承的方式。这种方法通过使用 call
或 apply
方法改变父类构造函数的 this
上下文,从而实现子类实例共享父类属性的目的。
// 定义一个基础构造函数
function Animal(name) {this.name = name;
}// 给 Animal 添加一个方法
Animal.prototype.speak = function() {console.log("我是" + this.name);
};// 定义一个继承自 Animal 的构造函数
function Dog(name, breed) {Animal.call(this, name); // 调用父类构造函数并绑定 thisthis.breed = breed;
}// 给 Dog 添加一个方法
Dog.prototype.bark = function() {console.log("汪汪汪!");
};// 创建一个 Dog 实例
var myDog = new Dog("旺财", "拉布拉多");
myDog.speak(); // 输出: 我是旺财
myDog.bark(); // 输出: 汪汪汪!console.log(myDog.name); // 输出: 旺财
console.log(myDog.breed); // 输出: 拉布拉多
12.7.3、组合继承
组合继承(也称为伪经典继承)是 JavaScript 中一种常用的继承方式,它结合了原型链继承和构造函数继承的优点。通过原型链实现方法的继承,通过借用构造函数实现属性的继承。这样,既可以保证实例间方法的共享,又可以保证实例拥有自己的属性。
// 定义一个基础构造函数
function Animal(name) {this.name = name;
}// 给 Animal 的原型添加一个方法
Animal.prototype.speak = function() {console.log("我是" + this.name);
};// 定义一个继承自 Animal 的构造函数
function Dog(name, breed) {Animal.call(this, name); // 调用父类构造函数并绑定 thisthis.breed = breed;
}// 设置 Dog 的原型为 Animal 的实例,实现方法继承
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog; // 修复构造函数指向// 给 Dog 的原型添加一个方法
Dog.prototype.bark = function() {console.log("汪汪汪!");
};// 创建一个 Dog 实例
var myDog = new Dog("旺财", "拉布拉多");
myDog.speak(); // 输出: 我是旺财
myDog.bark(); // 输出: 汪汪汪!console.log(myDog.name); // 输出: 旺财
console.log(myDog.breed); // 输出: 拉布拉多
12.7.4、原型式继承
原型式继承是 JavaScript 中一种基于原型对象的继承方式。通过复制(浅拷贝)一个对象的原型来创建新对象,从而实现继承。原型式继承适用于不需要单独设置构造函数,且不需要调用父对象构造函数的场景。
// 定义一个基础对象
var animal = {name: "未知",speak: function() {console.log("我是" + this.name);}
};// 定义一个继承自 animal 的新对象
function object(o) {function F() {}F.prototype = o;return new F();
}var dog = object(animal);
dog.name = "旺财";// 调用继承的方法
dog.speak(); // 输出: 我是旺财console.log(dog.name); // 输出: 旺财
注意,原型式继承只适用于对象字面量或已有对象的继承,不适用于构造函数的继承。此外,由于原型式继承是通过浅拷贝实现的,如果原型对象中包含引用类型的属性,那么子对象和原型对象将共享这些属性,可能会导致意外的行为。
12.7.5、寄生式继承
寄生式继承是 JavaScript 中一种基于原型式继承的继承方式,它在原型式继承的基础上,增加了对新对象的扩展,使得新对象不仅具有父对象的属性和方法,还可以具有自己的属性和方法。
// 定义一个基础对象
var animal = {name: "未知",speak: function() {console.log("我是" + this.name);}
};// 定义一个继承自 animal 的新对象
function createDog(name) {var dog = Object.create(animal); // 使用原型式继承创建新对象dog.name = name; // 添加自己的属性dog.bark = function() { // 添加自己的方法console.log("汪汪汪!");};return dog; // 返回新对象
}var myDog = createDog("旺财");
myDog.speak(); // 输出: 我是旺财
myDog.bark(); // 输出: 汪汪汪!console.log(myDog.name); // 输出: 旺财
注意,寄生式继承虽然可以实现对新对象的扩展,但它仍然是基于原型式继承的,因此也存在原型式继承的局限性,如浅拷贝导致的引用类型属性共享问题。
12.7.6、寄生组合式继承
寄生组合式继承(也称为寄生式组合继承)是 JavaScript 中一种结合了寄生式继承和组合继承的继承方式。它既借鉴了寄生式继承的优点,又借鉴了组合继承的优点,实现了方法和属性的继承。
// 定义一个基础构造函数
function Animal(name) {this.name = name;
}// 给 Animal 的原型添加一个方法
Animal.prototype.speak = function() {console.log("我是" + this.name);
};// 定义一个继承自 Animal 的构造函数
function Dog(name, breed) {Animal.call(this, name); // 调用父类构造函数并绑定 thisthis.breed = breed;
}// 定义一个寄生式继承的函数
function inheritPrototype(subType, superType) {var prototype = Object.create(superType.prototype); // 创建对象prototype.constructor = subType; // 增强对象subType.prototype = prototype; // 指定对象
}// 使用寄生式继承实现 Dog 的原型继承
inheritPrototype(Dog, Animal);// 给 Dog 的原型添加一个方法
Dog.prototype.bark = function() {console.log("汪汪汪!");
};// 创建一个 Dog 实例
var myDog = new Dog("旺财", "拉布拉多");
myDog.speak(); // 输出: 我是旺财
myDog.bark(); // 输出: 汪汪汪!console.log(myDog.name); // 输出: 旺财
console.log(myDog.breed); // 输出: 拉布拉多
寄生组合式继承结合了寄生式继承和组合继承的优点,既可以实现方法的共享,又可以保证实例拥有自己的属性。这种方式在实际开发中被广泛应用,是一种非常实用的继承方式。
12.8、垃圾回收
垃圾回收(Garbage Collection)是自动内存管理的一个过程,JavaScript运行环境会负责自动释放那些不再使用的内存,以防止内存泄漏。
12.8.1、标记-清除(Mark-and-Sweep)算法
JavaScript中最常用的垃圾回收算法是记-清除算法。这种算法分为两个阶段:
-
标记阶段(Mark Phase):
从根(root)对象开始,递归地访问对象的属性。
访问到的对象都会被标记为“活动”或“可达”的。
如果一个对象没有被访问到,那么它就被认为是“不可达”的,即垃圾。
-
清除阶段(Sweep Phase):
遍历整个堆内存,找到所有未被标记的对象(即垃圾)。
释放这些垃圾对象占用的内存空间。
12.8.2、 引用计数(Reference Counting)
引用计数也曾一种流行的垃圾回收策略。这种策略通过跟踪每个对象被引用的次数来确定对象是否应该被回收。
- 当一个对象被另一个对象引用时,其引用计数加1。
- 当引用被删除或超出作用域时,引用计数减1。
- 当引用计数为0时,对象将被视为垃圾并被回收。
但是,引用计数算法有一个严重的问题:循环引用。当两个对象相互引用,且没有其他外部引用时,这两个对象的引用计数永远不会降到0,导致内存泄漏。因此,现代JavaScript引擎大多不再使用纯粹的引用计数算法。
12.8.3、V8引擎的垃圾回收
V8是Google开发的开源JavaScript引擎,它使用了一种基于标记-清除算法的垃圾回收策略,并进行了许多优化以提高性能。
V8引擎将内存分为新生代(Young Generation)和老年代(Old Generation)。新生代中的对象通常存活时间较短,而老年代中的对象则存活时间较长。V8针对这两个区域使用不同的垃圾回收策略。
- 新生代垃圾回收:主要使用Scavenge算法,该算法将新生代分为两个半区,每次只使用其中一个半区进行对象分配。当其中一个半区满时,V8会停止JavaScript执行,复制仍然存活的对象到另一个半区,并释放当前半区的内存。这个过程被称为Minor GC。
- 老年代垃圾回收:当对象在新生代中存活一段时间后被晋升到老年代。老年代的垃圾回收使用标记-清除或标记-压缩算法。标记-压缩算法在标记阶段之后,还会移动存活的对象以消除内存碎片,从而提高内存利用率。这个过程被称为Major GC。
12.8.4、优化垃圾回收性能
为了减少垃圾回收对性能的影响,可以采取以下策略:
减少对象创建:通过重用对象或使用对象池来减少对象创建的数量。
避免全局变量:全局变量的生命周期很长,容易导致内存占用过多。
及时解除引用:当不再需要某个对象时,及时将其引用设为null,以便垃圾回收器能够回收其内存。
使用WeakMap和WeakSet:这些数据结构允许你创建对对象的弱引用,当对象不再被其他强引用指向时,它们会自动被垃圾回收。
13、作用域
13.1、声明提前
变量的声明提前:使用var关键字声明的变量,会在所有的代码执行之前被声明(但是不会赋值),但是如果声明变量时不使用var关键字,则变量不会被声明提前
函数的声明提前:使用函数声明形式创建的函数 function 函数名(){} ,它会在所有的代码执行之前就被创建,所以我们可以在函数声明前来调用函数。使用函数表达式创建的函数,不会被声明提前,所以不能在声明前调用。
13.2、作用域
13.2.1、全局作用域
直接编写在script标签中的JavaScript代码,都在全局作用域
全局作用域在页面打开时创建,在页面关闭时销毁
在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建,我们可以直接使用
在全局作用域中:
创建的变量都会作为window对象的属性保存
创建的函数都会作为window对象的方法保存
全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问的到
var str="我是全局变量";
function fn(){console.log(str);
}
fn();
console.log(str);
13.2.2、函数作用域
调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
每调用一次函数就会创建一个新的函数作用域,它们之间是互相独立的
在函数作用域中可以访问到全局作用域的变量,在全局作用域中无法访问到函数作用域的变量
在函数中要访问全局变量可以使用window对象
function fn(){var str = "我是局部变量,在函数外部无法访问";console.log(str);
}
fn();
console.log(str);//报错,str is not defined
13.3、作用域链
多个上下级关系的作用域形成的链,它的方向是从下向上的(从内到外),查找变量时就是沿着作用域链来查找的。当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用,如果没有则向上一级作用域中寻找,直到找到全局作用域,如果全局作用域中依然没有找到,则会报错ReferenceError
查找一个变量的查找规则:
在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
再次执行2的相同操作,直到全局作用域,如果还找不到就抛出找不到的ReferenceError异常
var str="我是作用域链2层";
function fn(){var str = "我是作用域链1层";console.log(str);
}
fn();
存。
使用WeakMap和WeakSet:这些数据结构允许你创建对对象的弱引用,当对象不再被其他强引用指向时,它们会自动被垃圾回收。
13、作用域
13.1、声明提前
变量的声明提前:使用var关键字声明的变量,会在所有的代码执行之前被声明(但是不会赋值),但是如果声明变量时不使用var关键字,则变量不会被声明提前
函数的声明提前:使用函数声明形式创建的函数 function 函数名(){} ,它会在所有的代码执行之前就被创建,所以我们可以在函数声明前来调用函数。使用函数表达式创建的函数,不会被声明提前,所以不能在声明前调用。
13.2、作用域
13.2.1、全局作用域
直接编写在script标签中的JavaScript代码,都在全局作用域
全局作用域在页面打开时创建,在页面关闭时销毁
在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建,我们可以直接使用
在全局作用域中:
创建的变量都会作为window对象的属性保存
创建的函数都会作为window对象的方法保存
全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问的到
var str="我是全局变量";
function fn(){console.log(str);
}
fn();
console.log(str);
13.2.2、函数作用域
调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
每调用一次函数就会创建一个新的函数作用域,它们之间是互相独立的
在函数作用域中可以访问到全局作用域的变量,在全局作用域中无法访问到函数作用域的变量
在函数中要访问全局变量可以使用window对象
function fn(){var str = "我是局部变量,在函数外部无法访问";console.log(str);
}
fn();
console.log(str);//报错,str is not defined
13.3、作用域链
多个上下级关系的作用域形成的链,它的方向是从下向上的(从内到外),查找变量时就是沿着作用域链来查找的。当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用,如果没有则向上一级作用域中寻找,直到找到全局作用域,如果全局作用域中依然没有找到,则会报错ReferenceError
查找一个变量的查找规则:
在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2
在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
再次执行2的相同操作,直到全局作用域,如果还找不到就抛出找不到的ReferenceError异常
var str="我是作用域链2层";
function fn(){var str = "我是作用域链1层";console.log(str);
}
fn();