前端 - JS基础知识

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>

输入输出

  1. 浏览器弹出警示框
alert('message');
  1. 浏览器控制台打印输出信息
console.log('message');
  1. 浏览器弹出输入框
prompt('请输入年龄');

变量

定义变量

使用var来定义变量:

var a;
console.log(a);  // undefined

var name = 'abc', age = 12;  // 声明多个变量

标识符

标识符的命令规则:

  1. 可以包含字母、数字、_$
  2. 严格区分大小写
  3. 不能以数字开头
  4. 不能是ES中的关键字或保留字
  5. 一般采取驼峰命名法

数据类型

JS是一种弱类型语言,不用提前声明变量的类型,程序在运行过程中,类型会被自动确定。

基本数据类型

基本数据类型 默认值 说明
Number 0 数字型,包含整型和浮点型
Boolean false 布尔型,truefalse等价于10
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

truefalse等价于10

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种方法:

  1. toString(),不适用于nullundefined,因为这两种类型没有方法,不能调用toString()
  2. String()
  3. 加号拼接
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()强制转换时,不同参数对应的结果:

  1. 字符串
    • 如果字符串是一个合法的数字,则直接转换为对应的数字
    • 如果字符串是一个非法的数字,则转换为NaN
    • 如果是一个空串或纯空格的字符串,则转换为0
  2. 布尔值
    • false转换为0
    • true转换为1
  3. null转换为0
  4. undefined 转换为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

对象

  1. 内建对象: 由ES标准中定义的对象,在任何的ES的实现中都可以使用:Math String Number Boolean Function Object….

  2. 宿主对象: 由JS的运行环境提供的对象,目前来讲主要指由浏览器提供的对象,比如BOM DOM

  3. 自定义对象: 由开发人员自己创建的对象

对象的基本操作

创建对象的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 {}

构造函数的执行流程:

  1. 立即创建一个新的对象
  2. 将新建的对象设置为函数中的this
  3. 逐行执行函数中的代码
  4. 将新建的对象返回
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);
};

原型对象

使用上面的方法虽然能解决方法被多次创建的问题,但是由于方法在全局作用域被定义,有以下缺点:

  1. 全局作用域命名空间被污染
  2. 定义在全局作用域并不安全

所以,更推荐使用原型对象解决该问题:

  • 我们所创建的每一个函数,解析器都会向函数添加一个属性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对象的情况可以分为:

  1. 以函数形式调用,thiswindow对象
  2. 以方法形式调用,this为调用方法的对象
  3. 以构造函数形式调用,this为新创建的对象
  4. 使用call()apply()方法时,this为指定的对象
posted @ 2022-01-19 14:47  lv6laserlotus  阅读(61)  评论(0)    收藏  举报