JavaScript基础
[Udemy课程|1~3章节内容]
[课程评价-⭐⭐⭐❎❎]
如果学习过一门基础编程语言,基础部分大概看一下了解一下JavaScript的独特语法和JS的发展与用途就可以了。这一整套教程是没有中文翻译的,其在B站的大多数视频是用谷歌机翻,虽然有时候翻译很草,但是有编程语言基础的人大概可以猜出它讲的是什么意思,所以这个课程即使内容十分适合新手也对编程新手有些许挑战,主要是机翻实在不能完全表达含义,或许我会在有时间的时候翻译一下最新的版本。
什么是JavaScript?
JavaScript是一个高级的面向对象的多范式编程语言。
JavaScript是用来做什么的?
计算机的三大核心是:
HTML、CSS、JavaScript。
HTML技术负责网页的框架,CSS技术负责网页的样式、JavaScript负责网页元素的动作。
JavaScript标准
JavaScript从ES5开始一直发展到今天的现代JavaScript,ES6(ES2015)/ES7(ES2016)/ES8(ES2017)/ES9(ES2018)/ES10(ES2019)/ES11(ES2020)…
ES5就是指ECMAScript。
ES5是十分重要的,因为大部分现代JavaScript规则是从ES5改进而来。
两个简单的输出函数
alert("JavaScript!"); //使用alert()函数使字符串输出在提示框上
console.log("JavaScript!"); //使用console对象的log方法将字符串输出在控制台中
函数定义的一些细节
var x = 10; //使用var也可以定义变量
const x = 10; //使用const也可以定义变量
//let和const都是在ES6中被引进的
let Person = 'jonas'; //这是对象的定义方式,在定义常规变量时,不要使用大写字母开头的变量名
let name = 'jonas'; //虽然系统允许这样定义变量名 但是运行会有一定的风险
let PI = 3.1415; //JavaScript中的常量虽然没有规定用全大写字母,但是行业规定建议如此
let myFirstJob = 'Programmer'; //定义变量的规范
let myCurrentJob = 'Teacher'; //这样定义变量可以使程序更加具有可读性
数据类型
Number
数字类型
一般都是浮点型的小数,即使定义的是一个整数,其实在系统中其表示的仍然是一个浮点数。
let age = 23;
let age = 23.0;
String
字符类型
它们只用于文本,总是把它们放在引号中,单引号和双引号都可。
Boolean
布尔类型
布尔值是一种逻辑类型,只存放true和false,用数字表示为0/1。
Undefined
未定义值
指变量的数据类型定义了但是值未被定义
这表示这个变量还没有被定义,只是我们声明的一个变量,但是并未赋值,例如:
let children;
Null
空值
指变量的数据类型定义了且值是一个空值
这意味着空值,和上类型有些许相同。
typeof null;
//上面的代码会告诉我们null真正的数据类型是一个Object,但是这其实被认为是一个JavaScript的BUG,由于历史遗留问题,这个BUG到今天仍然没有被解决,这里理应返回null。
Symbol
这个类型在ES2015中首次被添加,它定义了一个唯一值,并且无法被改变。
BigInt
这个类型在ES2020中首次添加,对于太大的整形数据可以由它来存放。
动态类型
dynamic typing
当我们定义类型时,我们不需要手动定义数据类型。例如:
let abc = true;
在JavaScript中有类型的是值,而不是变量,所以变量只是存储有类型的值。
也就是说一个变量在一些时候是一个Number类型,但是在有些时候可能是另外一个类型的。
数据类型定义
数据类型的定义可以使用var、let、const,这其中有着一些不同之处。
当我们想在程序头定义变量,但在后面的程序中为它们分配实际值时,我们使用let。例如:
let age;
age = 30;
age = 31;
当我们想声明不应该在任何时间改变的变量时,我们使用const(常数变量)。
const birthYear = 20020428;
birthYear = 1990; //当我们强行想改变const定义的变量时,会在控制台中得到一个类型错误TypeError
const a; //这样也是错误的,我们不能声明一个空的常数变量,会得到一个初始化错误SyntaxError
所以在程序编写过程中我们应该将未来程序中会改变的值用let定义,但是不会改变的值用const定义,这样可以保证程序的安全性。
而JavaScript的第三种定义方式var,应该完全避免。
var在ES6之前被广泛应用。
var job = 'programmer';
虽然var和let运作起来差不多,其实之间有很大的不同。
let是一块作用域而var是函数范围。
job = 'a';
//这样定义虽然可以运行,但是JavaScript其实是在全局中添加了一个属性job,其值为a
JavaScript的基本运算符
算术运算符
+ - * /
2 ** 3; //指2的3次方,即2*2*2
console.log(2 * 2, 2 * 3, 2 ** 3);
//应该会在控制台输出 4 6 8 的结果
const firstName = 'Du';
const lastName = 'Meijie';
console.log(firstName + lastName);
//这样可以输出'DuMeijie'
typeof firstName;
//得到String,typeof操作用于得到变量的类型
赋值运算符
=
let x;
x += 10; //指x = x + 10;
x *= 10; //指x = x * 10;其他运算符差不多
x++; //x = x + 1;先用值再自加
x--; //x = x - 1;
++x; //x = x + 1;先自加再用值
--x; //x = x - 1;
比较运算符
> >= <= < == != === !!=
const ageJonas = 46;
const ageSarsh = 19;
console.log(ageJonas > ageSarsh); //输出true
//在实际开发中我们会把比较出来的值放在一个变量中而不会将它在控制台中输出
运算符的优先级和结合律
const now = 2037;
const ageJonas = now - 1991;
const ageSarah = now - 2018;
console.log(now - 1991 > now - 2018); //得出true
const averageAge = (ageJonas + ageSarsh) / 2; //得出32.5
在MDN中可以看到有关运算符优先级和结合律的相关说明:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_precedence
字符串
const firstName = 'Jonas';
cosnt job = 'teacher';
cosnt year = 2037;
const birthYear = 1991;
cosnt jonas = "I'm " + firstName + ', a' + (year - birthYear) + 'years old' + job + '!';
console.log(jonas);
//当字符串中含有单引号时,使用双引号定义字符串变量
//你也可以使用字符串模板来创建并使用
const jonasNew = `I'm ${firstName}, a ${year - borthYear} year old ${job}!`;
console.log(jonasNew);
//也可以直接在反撇号中书写
console.log(`Just a regulat string...`);
//这样书写可以换行
console.log('String with \n\
multiple \n\
lines');
//当然在反撇号中也可以
console.log(`String
multiple
lines`);
条件控制语句
const age = 19;
const isOleEnough = age >= 18;
if(isOldEnough) {
//判断年龄是否达到可以学习驾照的标准
console.log('Sarah can start driving license!');
} else {
//如果年龄没有达到标准运行下面的语句
const yearsLeft = 18 - age;
console.log(`Sarah is too young. Wait anothetr ${yearLeft} years.`);
}
const birthYear = 1998;
if(birthYear <= 2000) {
let century = 20;
} else {
let century = 21;
}
console.log(century); //这是不允许的,代码块里面的变量是不可以被外面的函数访问的
类型转换
Number()
将类型转换为数字类型
const inputYear = '1991';
console.log(Number(inputYear) + 18);
console.log(Number('Jonas')); //这样将会输出NaN,意思是Not a Number,当涉及数字操作失败时我们产生了一个新的数字,所以,NaN不是一个数字,它意味着无效的数字
console.log(typeof NaN); //但是我们查看NaN的类型,会发现系统认为它是一个Number类型,它是一个Number类型,但是它是一个无效的数字
String()
将类型转换为字符串类型
Boolean()
将类型转换为布尔值类型
强制类型转换
当一个运算符处理两个值有不同的类型的情况时,JavaScript会在幕后将其中一个值强制转换以匹配另一个值,好在最后进行运算时可以执行该操作。
console.log('I am' + 23 + ' years old');
//在这条语句中23是一个Number类型,但是+运算符将23强制转换为字符串以方便进行运算,这种情况在模板中也适用
console.log('I am' + String(23) + ' years old');
console.log('23' - '10' - 3); //得10,在这条语句中-运算符和+运算符不一样,+运算符将Number类型转换为String,-运算符相反
运算符强制转换的前提是,转换的类型方便运算符后面的计算或者能在不影响运算功能的同时添加一个新的功能。
假值与真值
在JavaScript中有五种假值(false):
0 '' undefined null NaN
除了上面的值都是真值。
console.log(Boolean(0)); //false
console.log(Boolean(undefined)); //false
console.log(Boolean('Jonas')); //true
console.log(Boolean({})); //true
在进行逻辑运算符时或者有一个逻辑上下文时,JavaScript总是会进行隐式计算,即强制类型转换。
例如:
const money = 0;
//在if的条件判断中,JavaScript会将获得的值进行隐式计算,也就是强制类型转换为布尔值
if (money) {
console.log("Don't spend it all ;");
} else {
console.log('You should get a job!');
}
let height;
if(height) {
console.log('YAY! Height is defined');
} else {
console.log('Height is UNDEFINED');
}
//输出了'Height is UNDEFINED',因为height声明了确没有赋值,而undefined值是一个假值
//但当我们给height附一个0,我们还是会得到'Height is UNDEFINED',因为0也是一个假值,所以在这里我们创建了一个应用层面的BUG,我们没有想到高度为0的情况,我们将在逻辑运算符中详细讲解这个应用层面BUG的解决方法
逻辑运算符
===
判断是否两边的表达式完全等于,完全一样,在这条表达式中不会进行任何的强制类型转换,也就是隐式计算。
==
判断是否两边的表达式相等,但这条表达式中两边的表达式会进行强制类型转换。
const age = 18;
if(age === 18)
console.log('You just became an adult:D');
'18' == 18 //true
'18' === 18 //false
在开发过程中应该尽量避免使用==,而是使用===。
!==
判断等式两边的表达式是否不相等,它将会进行强制类型转换。
!=
上面这个不会进行强制类型转换。
&&
AND,两边的表达式都为真才为真,一个为假就是假。
||
OR,两边的表达式有一个为真就是真,都为假才为假。
!
NOT,表达式为真则为假,为假则为真。
从网页上获取一个值
我们可以通过prompt()函数来做到这一点。
在程序网页加载时,网页会弹出一个框,并显示你在prompt函数中输入的值和一个文本输入框,提供了一个输入,你可以用一个变量存储它。
const favourite = prompt("What's your favourite number?");
console.log(favourite); //这将会在控制台输出你刚刚在网页输入的值
console.log(typeof favourite); //得到string,这说明输入的值是string类型的
switch语句
const day = 'monday';
switch(day) {
case 'monday': //相当于if(day === 'monday'),注意这里是不会隐式计算的
console.log('Plan course structure');
console.log('Go to coding meetup');
break;
case 'tuesday':
console.log('Prepare theory videos');
break;
case 'wednesday':
case 'thursday':
console.log('Write code examples');
break;
case 'friday':
console.log('Record videos');
break;
case 'saturday':
case 'sunday':
console.log('Enjoy the weekend :D');
break;
default:
console.log('Not a valid day!');
}
语句和表达式
3 + 4
1991
true && false && !false
'23 is bigger'
上面列出的都是表达式,表达式会产生一个值
if(23 > 10) {
const strconst str = '23 is bigger';
}
这就是一个语句
在模板中我们只能插入表达式而不能插入语句
console.log(`I'm ${2037 - 1991} years old`);
条件运算符
const age = 23;
age >= 18 ? console.log('I like to drink wine') : console.log('I like to drink water');
//相当于
if (age >= 18) {
console.log('I like to drink wine');
} else {
console.log('I like to drink water');
}
//但是需要注意的是,三元运算符是一种表达式而不是语句,所以我们可以在模板中使用它,也可以直接赋值给变量,比如:
const age = 23;
const drink = age >= 18 ? 'wine' : 'water';
console.log(drink); //输出'wine',这使代码更加精简了,可读性也不差
console.log(`I like to drink ${age >= 18 ? 'wine' : 'water'}`);
//但当我们需要条件判断,并且伴随巨大的代码块时,我们仍然需要if..else..
JavaScript版本
1995 Mocha
1996 Livescript(JavaScript)
IE浏览器抄袭了网景公司创造了JScript
人们意识到需要标准化JavaScript语言,所以在1997年ECMA提交了ECMAScript或者叫做ES1,这是最早的JavaScript标准。
1997 ES1
2009 ES5(在ES5中更新发布了很多有用的新特性)
2015 ES2015或ES6(该版本是有史以来最大的一次更新)
2016 ES2016(后来官方依照每年一更的周期进行)
2017 ES2017
…
因为JavaScript是向后兼容的,所以今天的浏览器js引擎依旧可以读懂以前版本的代码。
但是JavaScript仍然含有许多奇怪的BUG且不会向前兼容,这意味着用户使用旧的浏览器访问我们的网页会有一定的问题。
现在我们将ES5称为旧浏览器,它只在IE9和更老的版本中存在。
将ES6即更新的浏览器叫做现代浏览器,大多数的特性在现代浏览器中是支持的。
我们可以在
https://kangax.github.io/compat-table/es6/
中找到JavaScript核心和浏览器的支持情况。
严格模式
严格模式可以帮助开发者写出更加安全的代码。
'use strict'; //将这条语句写在脚本的开头以开启严格模式,若在这条语句之前有任何代码严格模式都不会被激活
我们还可以只在特定的函数或者代码块中激活严格模式。
'use strict';
let hasDriversLicense = false;
cosnt passTest = true;
if (passTest) {
DriversLincense = true; //这里故意写了一个错误,以方便严格模式的演示
}
if (hasDriversLicense) {
console.log('I can drive');
}
//当我们运行上面的代码时,控制台报错会具体指出程序的哪一行代码出现了问题,这将方便开发者找到那些BUG
const interface = 'Audio';
const private = 524;
//当运行上面这条语句时,控制台会报错,因为interface是一个保留关键字,在未来,类中可能会有一些叫做接口字段的东西,private同理
实际上严格模式还规定了函数和对象的使用规范。
函数
函数在代码块中包含了一行或多行完整的代码,这些代码会在将来的程序中多次复用,我们将其放在函数的代码块中方便在其他位置调用。
function logger() { //在这里我们声明了函数,并为函数写了一些功能,打印输出'My name is Jonas'
console.log('My name is Jonas');
}
logger(); //在这里我们调用了函数,它将在控制台打印输出'My name is Jonas'
logger(); //当然,我们可以多次调用
这将大大降低一个程序的耦合度,还会增加整个代码的可读性。
function fruitProcessor(apples, oranges) {
//我们在这里添加了两个形式参数,我们调用函数时,写上实际参数,实参传给形参
console.log(apples, oranges);
const juice = `Juice with ${spples} apples and ${oranges} oranges.`;
return juice; //根据代码块中的语句得到了juice,然后函数将juice传回调用处
}
//比如:
fruitProcessor(5, 0);
//我们将会得到:5 0
//这是console.log打印的数据,那么juice呢,实际上juice的数据传回到了调用处,但是我们还需要一个变量存储它
const appleJuice = fruitProcessor(5, 0);
console.log(appleJuice);
//我们得到了'Juice with 5 apples and 0 oranges.'
//我们也可以直接这样
console.log(fruitProcessor(5, 0));
函数表达式
//函数声明
function calcAge1(birthYear) {
return 2037 - birthYear;
}
const age1 = calcAge1(1991);
//函数表达式
const calcAge2 = function(birthYear) {
//匿名函数(这一部分被看作是一个表达式,一个值,这十分重要)
return 2037 - birthYear;
}
const age2 = calcAge2(1991);
console.log(age1, age2); //得出46 46
//函数不是一个字符串或者一个类型,而是一个值,因为是一个值,我们可以把它存储在变量中
//函数表达式是含有一个值的,我们将其值附给了变量calcAge2
//你可以在声明函数前调用该函数,但是不能在函数表达式之前调用
箭头函数
箭头函数使函数表达式更短,因此写起来更快。
const calcAge3 = birthYear => 2037 - birthYear;
const age3 = calcAge3(1991); //当然,我们可以调用该函数
console.log(age3);
这种函数本质上还是函数表达式,所有这些都是一个值,而且这个函数表达式的返回是隐式发生的,2037 - birthYear这个值会自动返回。
假设我们要计算一个人的退休时间:
const yearsUntilRetirement = birthYear => {
const age = 2037 - birthYear;
const retirement = 65 - age;
return retirement;
}
console.log(yearsUntilRetirement(1991));
如果我们需要多个参数
const yearsUntilRetirement = (birthYear, firstName) => {
const age = 2037 - birthYear;
const retirement = 65 - age;
return `${firstName} retires in ${retirement} years`;
}
console.log(yearsUntilRetirement(1991, 'Jonas'));
当函数功能简单,可以用箭头函数。
在一个函数中调用函数
function cutPieces(fruit) {
return fruit * 4;
}
function fruitProcessor(apples, oranges) {
const applePieces = cutPieces(apples);
const orangePieces = cutPieces(oranges);
const juice = `Juice with ${applePieces} pieces of apple and ${orangePieces} pieces of orange.`;
return juice;
}
console.log(fruitProcessor(2, 3));
上面代码的运作方式:
fruitProcessor(2, 3);
//上面代码的数字将会替换函数中的apples和oranges参数,分别为2,3
//apples、oranges形式变量会被cutPieces函数引用,它们会替换fruit参数
//在cutPieces函数体运行后,值会变成8,12并分别存储在applePieces和orangePieces中
//然后变量传到字符串中并附给juice,最后返回juice,在控制台中输出juice
//`Juice with 8 pieces of apple and 12 pieces of orange.`
注意函数在return之后就会结束。
数组
数组定义
const friends = ['Michael','Steven','Peter'];
const years = new Array(1991, 1984, 2008, 2020);
数组调用,数组元素调用
friends[0] //调用friends数组的第一个元素,得Michael
friends[2] //得Peter
也可以使用数组的方法
friends.length //得3,它得到了数组的长度
friends[friends.length - 1] //这将永远调用数组的最后一个元素
改变数组的值
friends[2] = 'Jay'; //这将会替换原来的元素
但我们不能这样做
friends = ['Bob','Alice'];
数组可以存储不同类型的值,甚至数组
const hh = 123;
const jonas = ['Jonas', 2037 - 1991, hh, friends];
基本的数组方法
const friends = ['Michael','Steven','Peter'];
friends.push('Jay'); //在数组的末尾添加一个元素
friends.unshift('John'); //在数组的开头添加一个元素
friends.pop(); //删除数组的最后一个元素
friends.shift(); //删除数组的第一个元素
['Michael','Steven']
friends.indexOf('Steven'); //在数组中索引'Steven'元素,得1
friends.indexOf('Bob'); //在数组中索引'Bob'元素,没有找到得-1
friends.includes('Steven'); //查看数组中是否包含元素,返回布尔值,计算过程不会进行强制类型转换
对象
在数组中我们有时会遇到这样的情况,需要创建一个对象含有多个属性的容器,如果我们没有对象用数组表示就是下面这样:
const jonasArray = [
'Jonas', //firstName
'Schmedtmann', //lastName
2037 - 1991, //age
'teacher', //job
['Michael', 'Peter', 'Steven'] //friends
]
如果用对象表示则是这样的,它由多个键值对组成:
const jonas = {
firstName: 'Jonas',
lastName: 'Schmedtmann',
age: 2037 - 1991,
job: teacher,
friends: ['Michael', 'Peter', 'Steven']
//键: 值
};
与数组最大的区别是在对象中,这些值的顺序当我们想要检索它们时根本不重要。
如何操作对象中的数据
在对象中如何检索数据:
const jonas = {
firstName: 'Jonas',
lastName: 'Schmedtmann',
age: 2037 - 1991,
job: 'teacher',
friends: ['Michael', 'Peter', 'Steven']
//键: 值
};
console.log(jonas.lastName); //得'Schmedtmann',通过键来得到值
console.log(jonas['lastName']); //得'Schmedtmann',中括号类可以存放一个表达式
const nameKey = 'Name';
console.log(jonas['first' + nameKey]); //可以这样来获取firstName键的值
console.log(jonas['last' + nameKey]); //可以这样来获取lastName键的值
const interstedIn = prompt('What do you want to know about Jonas? Choose between firstName, lastName, age, job, and friends');
console.log(jonas[interstedIn]);
//这样,当用户想要得到jonas的job时在弹出框输入job就可以得到'teacher'值
//但是在使用
console.log(jonas.interstedIn);
//输入job会得到undefined,因为jonas没有一个键叫做interstedIn,所以会得到未定义
//也就是说只有在[]中,JavaScript才知道表达式的存在
//当用户输入了一个不存在的值,我们的程序会出现一些问题,我们这样来修改它
if(jonas[interstedIn]) { //任何不为空、不为0、不为未定义的数字都为真
console.log(jonas.interstedIn);
} else {
console.log('Wrong request! Choose between firstName, lastName, age, job, and friends');
}
//当用户尝试不存在的值时现在就会显示这一句话'Wrong request! Choose between firstName, lastName, age, job, and friends'
如何为对象添加新的属性:
jonas.location = 'Portugal';
jonas['twitter'] = '@BasHSnbaba';
//上面这两种方法都可以添加新的属性
学习了以上,我们可以做出这个:
console.log(`${jonas.firstName} has ${jonas.friends.length} friends, and his best friend id called ${jonas.friends[0]}`);
//输出'jonas has 3 friends, and his best friend id called Michael'
For循环
//假如要表示你在健身房做了10次举重
for(let i = 1;i <= 10; i ++) {
console.log(`Lefting weights repetition ${i}`);
}
//输出:
//Lefting weights repetition 1
//Lefting weights repetition 2
//Lefting weights repetition 3
//Lefting weights repetition 4
//Lefting weights repetition 5
//Lefting weights repetition 6
//Lefting weights repetition 7
//Lefting weights repetition 8
//Lefting weights repetition 9
//Lefting weights repetition 10
//for循环在数组中的应用,将数组中的元素全部输出
const jonasArray = [
'Jonas',
'Schmedtmann',
2037 - 1991,
'teacher',
['Michael', 'Peter', 'Steven']
];
const types = [];
for (let i = 0; i < jonasArray.length; i++) {
console.log(jonasArray[i], typeof (jonasArray[i]));
types[i] = typeof (jonasArray[i]);
}
console.log(types);
另一个例子,知出生日期得出年龄:
const years = [1991, 2007, 1969, 2020];
const ages = [];
for (let i = 0; i < years.length; i++) {
ages[i] = 2037 - years[i];
}
console.log(ages);
continue与break
continue将跳过当前循环,提前进入下一个循环。
break会直接终止循环。
For双循环与逆序循环
我们想将0,1,2,3,4输出为4,3,2,1,0:
const num = [1, 2, 3, 4, 5];
for (let i = num.length - 1; i >= 0; i--) {
console.log(num[i]);
}
若我们还想让每次输出都输出五次:
const num = [1, 2, 3, 4, 5];
for (let i = num.length - 1; i >= 0; i--) {
for (let j = 0; j < 5; j++) {
console.log(num[i]);
}
}
while循环
也叫当循环,其条件就是当条件为真时运行语句。
while的循环变量需要在循环体外定义。
let rep = 1;
while (rep <= 10) {
console.log(`Lifting weights repetition ${rep}`);
rep++;
}
一个掷骰子的例子,直到骰到6为止:
let dice = Math.trunc(Math.random() * 6) + 1;
//Math.random()会得到一个大于等于0小于1的小数,如果乘6会得到一个大于等于0小于6的小数,加1得到一个大于等于1小于7的小数,最小为1,最大为6.9999,然后通过Math.trunc()去除了小数部分只留整数部分
while (dice !== 6) { //条件:骰到6就结束循环
console.log(`You rolled a ${dice}`);
dice = Math.trunc(Math.random() * 6) + 1; //更新循环变量
if (dice === 6) {
console.log('Loop is about to end...');
}
}