前端 - JS基础知识
初识JavaScript
浏览器执行JS
-
渲染引擎: 用来解析HTML与CSS ,俗称内核,比如chrome浏览器的blink ,老版本的webkit
-
JS引擎: 也称为JS解释器。用来读取网页中的JavaScript代码,对其处理后运行,比如chrome浏览器的V8
浏览器本身并不会执行JS代码,而是通过内置JavaScript引擎(解释器)来执行JS代码。JS 引擎执行代码时逐行解释每一句源码(转换为机器语言) ,然后由计算机去执行,所次JavaScript语言归为脚本语言,会逐行解释执行。
JS的组成
- ECMAScript : 规定了JS的编程语法和基础核心知识,是所有浏览器厂商共同遵守的一套JS语法工业标准。
- DOM(文档对象模型): 是W3C组织推荐的处理可扩展标记语言的标准编程接口。通过DOM提供的接口可以对页面上的各种元素进行操作(大小、位置、颜色等)。
- BOM (浏览器对象模型): 它提供了独立于内容的、可以与浏览器窗口进行互动的对象结构。通过BOM可以操作浏览器窗口,比如弹出框、控制浏览器跳转、获取分辨率等。
JS的书写位置
- 行内式
<button onclick="alert('world');">Hello</button>
- 内嵌式
<script>
alert('Hello world!');
</script>
- 外部js,此时标签内的代码无效
<script src="/js/my.js"></script>
输入输出
- 浏览器弹出警示框
alert('message');
- 浏览器控制台打印输出信息
console.log('message');
- 浏览器弹出输入框
prompt('请输入年龄');
变量
定义变量
使用var来定义变量:
var a;
console.log(a); // undefined
var name = 'abc', age = 12; // 声明多个变量
标识符
标识符的命令规则:
- 可以包含字母、数字、
_、$ - 严格区分大小写
- 不能以数字开头
- 不能是ES中的关键字或保留字
- 一般采取驼峰命名法
数据类型
JS是一种弱类型语言,不用提前声明变量的类型,程序在运行过程中,类型会被自动确定。
基本数据类型
| 基本数据类型 | 默认值 | 说明 |
|---|---|---|
Number |
0 |
数字型,包含整型和浮点型 |
Boolean |
false |
布尔型,true和false等价于1和0 |
String |
"" |
字符串型 |
Undefined |
undefined |
声明var a;,但是没有赋值,此时a的值为undefined |
Null |
null |
var a = null;,声明a的值为null |
Number
八进制与十六进制:
var num = 010; // 八进制0开头
console.log(num); // 8
num = 0xa; // 十六进制0x开头
console.log(num); // 10
范围:
console.log(Number.MAX_VALUE); // 1.7976931348623157e+308
console.log(Number.MIN_VALUE); // 5e-324
3个特殊值:
console.log(Infinity); // 无穷大,大于任何数值
console.log(-Infinity); // 无穷小,小于任何数值
console.log(NaN); // Not a number, 表示一个非数值
console.log(Number.MAX_VALUE * 2); // Infinity
console.log(-Number.MAX_VALUE * 2); // -Infinity
console.log('123abc' - 10); // NaN
判断是不是数字:
console.log(isNaN(123)); // false
console.log(isNaN('abc')); // true
String
字符串类型需要单引号或双引号,JS中更推荐单引号。
var str = 'abc';
str = "def";
单引号和双引号的嵌套:
var str = "123'456'";
console.log(str); // 123'456'
str = '555"abc"666';
console.log(str); // 555"abc"666
转义字符:
| 转义字符 | 说明 |
|---|---|
\n |
换行 |
\\ |
反斜杠 |
\' |
单引号 |
\" |
双引号 |
\t |
缩进 |
\b |
空格 |
字符串常用方法:
var str = 'Hello world.'
// 长度length
console.log(str.length); // 12
// 字符串拼接
str += '123';
console.log(str); // Hello world.123
console.log('test:' + true); // test:true
console.log('12' + 34); // 1234
Boolean
true和false等价于1和0
console.log(true + 1); // 2
console.log(false + 1); // 1
Undefined
var str;
console.log(str); // undefined
console.log(str + 'abc'); // undefinedabc
console.log(str + 12); // NaN
console.log(str + true); // NaN
Null
var str = null;
console.log(str); // null
console.log(str + 'abc'); // nullabc
console.log(str + 12); // 12
console.log(str + false); // 0
获取数据类型typeof
var num = 10;
console.log(typeof num); // number
var str = 'abc';
console.log(typeof str); // string
var num = 10;
console.log(typeof num); // number
var u;
console.log(typeof u); // undefined
var obj = null;
console.log(typeof obj); // object
// 从prompt获取的数据是string类型
var age = prompt('Your age?');
console.log(typeof age); // string
数据类型转换
转换为字符串
3种方法:
toString(),不适用于null和undefined,因为这两种类型没有方法,不能调用toString()String()- 加号拼接
var num = 10;
console.log(typeof num); // number
console.log(typeof num.toString()); // string
console.log(typeof String(num)); // string
console.log(typeof (num + '')); // string
转换为数字型
| 方式 | 说明 | 样例 |
|---|---|---|
parseInt()方法 |
转换为整数数字型 | parseInt('78') |
parseFloat()方法 |
转换为浮点数字型 | parseFloat('12.34') |
Number()强制转换 |
转换为数字型 | Number('123') |
| 隐式转换 | 利用算数隐式转换 | '123'-0 |
console.log(parseInt('120abc')); // 120
console.log(parseInt('12.34abc')); // 12
console.log(parseInt('aaa120abc')); // NaN
console.log(parseFloat('12.34abc')); // 12.34
console.log(Number('12.34')); // 12.34
console.log(Number('12.34abc')); // NaN
// 隐式转换 - / *
console.log(12 - '0'); // 12
console.log('22' * '2'); // 44
console.log('50' / '0'); // Infinity
使用Number()强制转换时,不同参数对应的结果:
- 字符串
- 如果字符串是一个合法的数字,则直接转换为对应的数字
- 如果字符串是一个非法的数字,则转换为NaN
- 如果是一个空串或纯空格的字符串,则转换为0
- 布尔值
false转换为0true转换为1
null转换为0undefined转换为NaN
转换为布尔型
- 表示空、否定的值都会被转换为
false,其余值都会被转换为true
console.log(Boolean(0)); // false
console.log(Boolean('')); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(null)); // false
console.log(Boolean(NaN)); // false
console.log(Boolean(-1)); // true
console.log(Boolean('0')); // true
console.log(Boolean('abc')); // true
对象
-
内建对象: 由ES标准中定义的对象,在任何的ES的实现中都可以使用:
MathStringNumberBooleanFunctionObject…. -
宿主对象: 由JS的运行环境提供的对象,目前来讲主要指由浏览器提供的对象,比如BOM DOM
-
自定义对象: 由开发人员自己创建的对象
对象的基本操作
创建对象的2种方法:
var obj = new Object();
var obj1 = {};
读取、删除属性值:
var stu = {};
stu.name = 'Lee';
stu.gender = 'male';
stu.age = 10;
console.log(stu); // {name: "Lee", gender: "male", age: 10}
// 读取属性值
console.log(stu.gender); // male
console.log(stu.grade); // undefined
// 删除属性值
delete stu.gender;
console.log(stu.gender); // undefined
属性名和属性值
对象的属性名可以不遵循标识符的规范:
var obj = {};
obj.var = 456;
console.log(obj.var); // 456
对于一些特殊的属性名,不能直接用obj.属性名的方式操作:
// obj.123 = 888; // 报错
obj['123'] = 888; // 需要用这种方法
var num = 123;
console.log(obj[num]); // 888
console.log(obj['123']); // 888
使用in运算符检查对象是否有对应的属性,用法: "属性名" in obj
console.log('123' in obj); // true
console.log('456' in obj); // false
使用字面量创建对象:
// 键值对方式,最后一个属性不需要逗号
// 属性名推荐不加引号,但如果是123这种特殊的属性,必须加引号
var obj = {
name: 'Lee',
gender: 'male',
'123': 888
};
console.log(obj); // {123: 888, name: "Lee", gender: "male"}
// 遍历对象的属性值
for(var attr in obj) {
console.log(obj[attr]);
}
基本数据类型和引用数据类型
JS中的变量都是保存在栈空间的,其中:
- 5种基本数据类型的值直接保存在栈空间
- 引用数据类型,即对象的值是保存在堆内存中的,变量保存的是对象在堆内存中开辟的空间的内存地址(对象的引用)
垃圾回收机制
当一个对象没有被任何变量属性引用时,则永远无法被操作。JS拥有自动的垃圾回收机制,会将这些对象从内存中销毁,我们只用将不需要使用的对象赋值为null即可。
运算符
算数运算符
| 运算符 | 描述 |
|---|---|
+ |
加 |
- |
减 |
* |
乘 |
/ |
除 |
% |
取余 |
不要直接对浮点数进行比较:
var num = 0.1 + 0.2;
console.log(num); // 0.30000000000000004
console.log(num == 0.3); // false
console.log(0.07 * 100); // 7.000000000000001
递增/递减运算符
与Java类似:
var num = 10;
console.log(num++); // 10
console.log(++num); // 12
console.log(num--); // 12
console.log(--num); // 10
比较运算符
除了常规的<, <=, >, >=之外,还有:
| 运算符 | 说明 |
|---|---|
== |
判断是否相等(会转型) |
!= |
判断是否不相等 |
=== |
值和数据类型都相同时返回true |
!== |
值和数据类型都相同时返回false |
console.log(123 == '123'); // true
console.log(123 != '123'); // false
console.log(123 === '123'); // false
console.log(123 !== '123'); // true
逻辑运算符
| 运算符 | 说明 |
|---|---|
&& |
逻辑与 |
|| |
逻辑或 |
! |
逻辑非 |
会短路:
console.log(123 && 456); // 456
console.log(null && 123); // null
console.log(123 || 456); // 123
console.log(null || undefined || 123); // 123
对非布尔值的运算
- 对非布尔值取反,会先将其转换为布尔值
var a = 123;
console.log(typeof a); // number
a = !a;
console.log(a); // false
- 对任意变量取反两次,就能将其转化为布尔值,效果和
Boolean()相同
var b = '';
b = !!b;
console.log(b); // false
- 逻辑与、逻辑或在对非布尔值进行运算时,会先将其转换为布尔值,然后再运算,并且返回原值
赋值运算符
用法与Java类似,包括:=, +=, -=, *=, /=, %=
运算符优先级
| 优先级 | 运算符 | 说明 |
|---|---|---|
| 1 | 小括号 | () |
| 2 | 一元运算符 | ++, --, ! |
| 3 | 算数运算符 | 先*, /, %, 后+, - |
| 4 | 关系运算符 | >, >=, <, <= |
| 5 | 相等运算符 | ==, !=, ===, !== |
| 6 | 逻辑运算符 | 先&&, 后` |
| 7 | 赋值运算符 | = |
| 8 | 逗号运算符 | , |
流程控制-分支
if分支
var year = 2100;
if(year % 400 == 0 || year % 4 == 0 && year % 100 != 0) {
console.log(year + '是闰年!');
}
else {
console.log(year + '是平年!');
}
else if的用法与Java类似。
三元表达式
与Java用法类似:
var num = 10;
console.log(num > 5 ? 'Bigger.' : 'Smaller.'); // Bigger.
switch语句
与Java用法类似:
var grade = 59;
var result;
switch(grade / 10) {
case 10:
case 9:
result = 'A';
break;
case 8:
case 7:
result = 'B';
break;
case 6:
result = 'C';
break;
default:
result = 'F';
}
console.log('成绩等级: ' + result); // 成绩等级: F
注意:
switch中的条件与case必须全等才会匹配,例如switch('3')不能匹配case(3)
流程控制-循环
for循环
与Java用法类似:
for(var i = 0; i < 10; i++) {
if(i % 2 == 0) {
continue;
}
console.log(i);
}
while循环
与Java用法类似:
var i = 0;
while(i < 10) {
if(i == 8) {
break;
}
console.log(i);
i++;
}
do-while循环
与Java用法类似,do-while循环至少执行一次:
var i = 0;
while(i < 10) {
console.log(i);
i++;
}
函数
函数也是对象,使用typeof检查,会返回function。
var func = new Function("console.log('function test');");
console.log(typeof func); // function
func(); // function test
一般不会采取上述的方式创建函数,而是使用函数声明。
函数声明
// 使用函数声明创建函数
function func1() {
console.log('function test1');
}
// 将匿名函数赋值给一个变量
var func2 = function() {
console.log('function test2');
}
func1(); // function test1
func2(); // function test2
立即执行匿名函数:
(function(a, b) {
console.log(a - b);
})(10, 15); // -5
函数参数
函数解析器不会检查实参的类型和数量。
function getSum(a, b) {
console.log(a + b);
}
getSum(123, '456'); // 123456
getSum('abc', true); // abctrue
getSum(1); // NaN(相当于1 + undefined)
getSum(12, 3, 4); // 15(前2个有效)
对象方法
对象的属性也可以是函数,此时称为对象的方法:
var obj = {
name: 'Lee',
gender: 'male',
'123': 888,
printName: function() {
console.log(obj.name);
}
};
obj.printName(); // Lee
全局作用域
- 全局作用域在页面打开时创建,在页面关闭时销毁
- 全局作用域中有一个全局对象
window,代表浏览器的窗口 - 在全局作用域中,所有变量都是
window对象的属性,所有函数都是window对象的方法
var a = 123;
function printNum() {
console.log(456);
}
console.log(window.a); // 123
window.printNum(); // 456
- 全局作用域的变量都是全局变量,在页面任意位置都能访问
变量声明提前
使用var声明的变量,会在所有代码被执行前创建(但不会被赋值),如果没有使用var则变量不会被提前声明。
a = 123;
console.log(a); // 123
// 报错
console.log(b); // ReferenceError: Can't find variable: b
b = 456;
如果使用var声明:
console.log(a); // undefined
var a = 'Hello!';
输出undefined,是因为上面的写法等价于:
var a;
console.log(a); // undefined
a = 'Hello!';
函数声明提前
使用函数声明function func(){}创建的函数,会在所有代码执行前被创建。
printNum(); // 123
function printNum() {
console.log(123);
}
使用函数表达式的函数则不行:
// 报错
printNum(); // TypeError: printNum is not a function. (In 'printNum()', 'printNum' is undefined)
var printNum = function() {
console.log(123);
};
这是因为上面的写法等价于:
var printNum;
printNum(); // TypeError: printNum is not a function. (In 'printNum()', 'printNum' is undefined)
printNum = function() {
console.log(123);
};
全局访问
有2个js文件:
/* my.js */
printNum();
function printNum() {
console.log(123);
}
/* test.js */
printNum();
全局访问:
<script src="js/my.js"></script>
<script src="js/test.js"></script>
会输出2次123。注意,顺序不能反过来,否则报错。
函数作用域
- 调用函数时创建函数作用域,函数执行完毕之后,函数作用域销毁
- 每调用一次创建一个全新的函数作用域,它们之间相互独立
- 函数作用域可以访问全局作用域的变量
- 函数作用域操作变量时,首先在自身作用域寻找,如果没找到,则向上一级作用域寻找,如果找到了全局作用域依然没有找到,则报错
var a = 10;
var b = 'abc';
var c = false;
func1(); // 20 abc true 10
function func1() {
var a = 20;
var c = true;
console.log(a); // 在当前func1作用域中找到了,为20
console.log(b); // 当前作用域没找到,在上一级全局作用域找到了,为abc
function func2() {
console.log(c); // 当前func2作用域没找到,在上一级func1作用域找到了,为true
}
func2();
// 如果想访问全局变量,使用window对象
console.log(window.a); // 10
}
- 函数作用域也有提前声明的特性,使用
var声明的变量会在函数所有代码执行前被创建,函数声明也会在函数内所有代码执行前执行 - 在函数中,没有用
var声明的变量,都会被创建为全局变量
function func() {
a = 10; // 没有使用var声明,所以a被创建为全局变量
}
func();
console.log(a); // 10
- 在函数中定义形参就相当于用
var声明了变量
var a = 123;
func1(); // 123
func2(); // undefined
function func1() {
console.log(a);
}
function func2(a) {
console.log(a);
}
this对象
解析器在调用函数时会隐式地传入一个参数this:
this指向一个对象,被称为函数执行的上下文对象- 根据函数调用方式的不同,
this所指向的对象也不同- 当以方法的形式被调用时,
this指向调用该方法的对象;显然,在以函数的形式被调用时,this指向window对象
- 当以方法的形式被调用时,
var func = function() {
console.log(this);
}
// 以函数的形式被调用时,this指向window对象(实际上此时func是window对象的方法)
func(); // Window {obj: undefined, Infinity: Infinity, window: Window, NaN: NaN, undefined: undefined, …}
var obj = {
name: 'Test this.',
printThis: func
};
// 以方法的形式被调用时,this指向调用它的对象
obj.printThis(); // {name: "Test this.", printThis: function}
使用this获取当前对象的属性值:
var name = 'Global name.';
function func() {
// console.log(name);
console.log(this.name); // 这里必须使用this,否则只会输出全局的Global name.
}
var obj1 = {
name: 'Object1.',
printName: func
};
var obj2 = {
name: 'Object2.',
printName: func
};
obj1.printName(); // Object1.
obj2.printName(); // Object2.
arguments对象
解析器在调用函数时除了会隐式地传入一个参数this之外,还会传入一个arguments对象:
arguments是一个类数组对象,即可以通过索引操作对象,但不是数组
function func(a, b) {
console.log(arguments instanceof Array); // false
console.log(arguments[0]); // 2
console.log(arguments[1]); // 4
console.log('a + b = ' + (a + b)); // a + b = 6
}
func(2, 4);
- 通过
length属性获取实参的个数
function func(a, b) {
console.log(arguments.length);
}
func(2, 4, 6); // 3
func(123); // 1
- 通过
callee属性获取正在指向的函数对象
function func(a, b) {
console.log(arguments.callee);
console.log('a + b = ' + (a + b));
}
func(2, 4);
/*
function func(a, b) {
console.log(arguments.callee);
console.log('a + b = ' + (a + b));
}
*/
使用工厂方法创建对象
function createPerson(name, age, gender) {
var person = {
name: name,
age: age,
gender: gender,
printName: function() {
console.log(this.name);
}
}
return person; // 返回新创建的对象
}
var obj1 = createPerson('Lee', 19, 'male');
console.log(obj1); // {name: "Lee", age: 19, gender: "male", printName: function}
obj1.printName(); // Lee
var obj2 = createPerson('A', 29, 'female');
console.log(obj2); // {name: "A", age: 29, gender: "female", printName: function}
obj2.printName(); // A
构造函数
创建构造函数Person(),专门用来创建Person对象。
- 构造函数习惯首字母大写
- 构造函数调用时,需要使用
new关键字
function Person() {
}
var per1 = Person();
var per2 = new Person(); // 构造函数需要使用new
console.log(per1); // undefined
console.log(per2); // Person {}
构造函数的执行流程:
- 立即创建一个新的对象
- 将新建的对象设置为函数中的
this - 逐行执行函数中的代码
- 将新建的对象返回
function Person(name, age) {
this.name = name;
this.age = age;
}
var per = new Person('Lee', 25);
console.log(per); // Person {name: "Lee", age: 25}
- 使用
Person()构造函数创建的对象,称为Person类的实例 - 所有类都是
Object类的后代,所以所有对象在用instanceof Object检查时,都会返回true
console.log(per instanceof Person); // true
console.log(per instanceof Object); // true
- 使用下面的构造函数创建对象时,每执行一次,都会创建一个新的
printName()方法
function Person(name, age) {
this.name = name;
this.age = age;
this.printName = function() {
console.log('I am ' + this.name);
};
}
var per1 = new Person('Lee', 25);
var per2 = new Person('A', 50);
per1.printName(); // I am Lee
per2.printName(); // I am A
console.log(per1.printName == per2.printName); // false
- 可以修改上述构造函数,将方法移到全局作用域中
function Person(name, age) {
this.name = name;
this.age = age;
this.printName = func;
}
function func() {
console.log('I am ' + this.name);
};
原型对象
使用上面的方法虽然能解决方法被多次创建的问题,但是由于方法在全局作用域被定义,有以下缺点:
- 全局作用域命名空间被污染
- 定义在全局作用域并不安全
所以,更推荐使用原型对象解决该问题:
- 我们所创建的每一个函数,解析器都会向函数添加一个属性
prototype,该属性对应一个对象,即原型对象 - 如果是普通函数,调用
prototype没有任何作用 - 当函数以构造函数的方式调用时,它所创建的对象都有一个隐式属性
__proto__,指向该构造函数的原型对象
function Person() {
}
function Dog() {
}
var per1 = new Person();
var per2 = new Person();
console.log(Person.prototype == Dog.prototype); // false
console.log(Person.prototype == per1.__proto__); // true
console.log(per1.__proto__ == per2.__proto__); // true
- 原型对象相当于一个公共的区域,同一个类的所有实例都能访问到此原型对象
- 于是可以将公共的内容统一设置到原型对象中
- 当访问对象的属性时,它会现在对象自身中寻找,如果没有找到,则会去原型对象中寻找
function Person(name) {
this.name = name;
}
// 为构造函数的原型对象添加属性和方法
Person.prototype.printName = function() {
console.log('I am ' + this.name);
};
Person.prototype.hello = 'Hello!';
var per1 = new Person('ABC');
var per2 = new Person('DEF');
per1.printName(); // I am ABC
per2.printName(); // I am DEF
console.log(per1.printName == per2.printName); // true
console.log(per1.hello); // Hello!
- 创建构造函数时,可以将对象共有的属性和方法放在构造函数的原型对象中,这样不用给每个对象都添加,也不会影响到全局作用域
检查对象是否有某个属性
- 使用
in检查对象是否有某个属性时,如果对象中没有,但是原型对象中有,也会返回true
console.log('hello' in per1); // true
console.log('printName' in per1); // true
- 可以使用对象的
hasOwnProperty()方法来检查对象自身是否有该属性
console.log(per1.hasOwnProperty('name')); // true
console.log(per1.hasOwnProperty('hello')); // false
hasOwnProperty()是对象的原型对象的原型对象的方法:
console.log(per1.hasOwnProperty('hasOwnProperty')); // false
console.log(per1.__proto__.hasOwnProperty('hasOwnProperty')); // false
console.log(per1.__proto__.__proto__.hasOwnProperty('hasOwnProperty')); // true
// 对象的原型对象的原型对象没有原型对象
console.log(per1.__proto__.__proto__.__proto__); // null
- 原型对象属于
Object类,也是对象,所以它也有原型对象。当我们使用per1对象的属性或方法时,如果对象本身没有,则去对象的原型对象中找;如果对象的原型对象也没有,则去对象的原型对象的原型对象中找,直到找到Object对象的原型对象,Object对象的原型对象没有原型对象
/*
* 这里obj只有一层原型对象,是因为obj对象是Object类
* Person类的对象有两层原型对象,是因为Person类不是Object类,它的原型对象才是Object类
*/
obj = {}; // 创建一个Object类的对象
console.log(typeof obj); // object
console.log(obj.__proto__); // {}
console.log(obj.__proto__.hasOwnProperty('hasOwnProperty')); // true
console.log(obj.__proto__.__proto__); // null
函数对象的call()与apply()方法
call()与apply()都是函数对象的方法,当函数对象调用时,都会执行函数本身。
function func() {
console.log('Test.');
}
func(); // Test.
func.call(); // Test.
func.apply(); // Test.
- 在调用
call()与apply()方法时,可以将一个对象作为第一个参数,此时,这个对象将会作为函数执行的this
function func() {
console.log(this.name);
}
obj1 = {name: 'obj1'};
obj2 = {name: 'obj2'};
func.call(obj1); // obj1
func.apply(obj2); // obj2
call()方法可以将实参在对象后依次传递,而apply()方法需要将实参封装到一个数组中统一传递
function func(a, b) {
console.log(this.name, a + b);
}
obj1 = {name: 'obj1'};
obj2 = {name: 'obj2'};
func.call(obj1, 1, 2); // obj1 3
func.apply(obj2, [3, 4]); // obj2 7
所以,this对象的情况可以分为:
- 以函数形式调用,
this为window对象 - 以方法形式调用,
this为调用方法的对象 - 以构造函数形式调用,
this为新创建的对象 - 使用
call()或apply()方法时,this为指定的对象

浙公网安备 33010602011771号