JavaScript Base Day - May 8

136 阅读17分钟

什么是JavaScript?

JacaScript是计算机高级语言中的解释型编程语言,也是脚本语言。

脚本语言是一种编程语言,它不具备开发操作系统的能力,它允许以简单、灵活、快速的方式编写程序。

脚本语言的一些关键特性:

  1. 易于编写:编写和修改代码很容易
  2. 解释执行:脚本语言的代码通常由解释器执行,而非编译成机器语言(JavaScript的解释器被称为JavaScript引擎,它属于浏览器的一部分,最为熟知的就是Chrome浏览器的V8引擎和Firefox的SpiderMonkey引擎)
  3. 动态类型:脚本语言往往是动态类型的,这提供了更大的灵活性
  4. 自动内存管理:大多数脚本语言提供了自动内存管理和垃圾回收机制,减少了内存管理的负担
  5. 交互式:脚本语言通常支持交互式编程,允许开发者于解释器进行实时交互
  6. 嵌入性:脚本语言可以很容易的嵌入到其他应用程序中,用于扩展和定制其他应用程序的功能
  7. 跨平台:许多脚本语言是跨平台的,可以在多种操作系统上运行,无需或者仅需做少许修改
  8. 适用于自动化:脚本语言常用于编写自动化脚本、执行重复性的任务,如文件操作等
  9. 快速开发:脚本语言适用于快速原型开发,可以快速实现和测试新想法
  10. 与现有代码集成:脚本语言可以轻松的和现有代码或系统集成,用于添加新功能或改进现有系统

我们看到脚本语言的一些关键特性,其实这也都是JavaScript的特点,我们知道JavaScript是一种动态类型、弱类型、基于原型的语言,并且内置支持反射。那么再来看下JavaScript的一些核心特性和特点:

  1. 动态类型:JavaScript不要求在变量声明时指定类型,在运行时自动确定
  2. 弱类型:JavaScript中的变量可以轻松的重新分配不同类型的值,而无需显式的转换
  3. *基于原型继承:JavaScript使用原型链来实现继承,每个对象都是某个其他对象的"实例"
  4. *反射:JavaScript提供了反射API,允许开发者检查和操作对象的属性和方法(反射是一种能力,它允许程序在运行时访问和操作对象内部的属性和方法。简单理解就是可以使用很多JS提供的方法,比如:
    1.  Object 类型:检查对象包含的属性的 Object.keys() 和 Object.getOwnPropertyNames() 还有定义或修改对象的属性的 Object.defineProperty() 以及获取对象属性的描述符 Object.getOwnPropertyDescriptor()
    2. Object.prototype:判断对象的属性是否属于自身的 hansOwnProperty() 和 获取实例对象的原型对象 isPrototyeOf() 
    3. Proxy 对象:允许你创建一个对象的代理,从而在访问对象的属性和方法时进行拦截和自定义操作
    4. Reflect API:Reflect 对象提供了与 Object 类型静态方法等价的一系列方法,不过它是为了操作对象而设计,它的方法返回的都是纯粹的值,而不会对对象进行任何修改
    5. Function 对象:通过Function对象,可以检查函数的name属性,以及使用 call()、apply()、bind()方法来控制函数的this上下文)
  5. 自动内存管理:JavaScript有自动垃圾回收机制,帮助管理内存使用
  6. *异步编程:JavaScript支持异步编程模式,如回调函数、Promise和async/await
  7. *单线程与事件驱动:JavaScript通常在单线程环境中运行,并采用事件循环机制,这使得它非常适合处理并发
  8. *函数是一等公民:在JavaScript中,将函数看作一种值,与其它值地位相同,凡是可以使用值的地方都能使用函数,比如:函数可以作为变量存储、作为参数传递给其他函数以及作为其他函数的返回值
  9. *闭包:JavaScript支持闭包,可以使函数访问和记忆(自由)变量
  10. *错误处理:JavaScript提供了 try...catch 语句来处理异常
  11. *标准库:JavaScript有一个丰富的标准库,包括数组、日期、数学、JSON、正则表达式
  12. *Web API 集成:在浏览器环境中,JavaScript可以访问DOM(文档对象模型)和BOM(浏览器对象模型),允许与网页元素交互
  13. 跨平台:JavaScript可以在多种环境中运行,包括浏览器、服务器(Node.js)、移动设备和桌面应用
  14. *模块化:JavaScript 支持模块化编程,允许代码重用和更好的组织结构(有不支持模块化的编程语言吗?)
  15. *ES6+的新特性:随着 ECMAScript 的更新,JavaScript 引入了许多现代编程语言的特性,如类、模块、箭头函数、模板字符串、默认参数、解构赋值等
  16. *性能:现代JavaScript引擎(如 Chrome 的V8)使用即时编译(JIT)技术,提供了高性能的代码执行
  17. *社区和生态系统:JavaScript拥有庞大的社区和丰富的生态系统,提供了大量的库和框架,如 React、Vue、Angular等
  18. *工具和自动化:有大量的工具可用于JavaScript开发,包括构建工具(Webpack)、包管理器(如npm和yarn)、代码编辑器和IDE

JavaScript的基本语法

1.  语句

JavaScript程序的执行单位为行(line),也就是一行一行的执行。
一般情况下,每一行就是一个语句。

**语句(statement)**是为了完成某种任务而进行的操作,比如下面就是一行赋值语句。

var a = 1 + 3;

1 + 3 叫做 表达式(expression),指一个为了得到返回值的计算式。

语句和表达式的区别:

  • 语句主要是为了进行某种操作,一般不需要返回值
  • 表达式则是为了得到返回值,一定会返回一个值

凡是 JavaScript 预期为值的地方都可以使用表达式。比如:赋值语句的等号的右边,预期是一个值,因此可以放置各种表达式。

2.  变量

变量是对“值”的具名引用。
变量就是为“值”起名,然后引用这个名字,就等同于引用这个值。

2.1 变量提升

JavaScript引擎的工作方式是,先全局解析代码,获取所有被声明的变量,然后再一行一行的往下运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫变量提升。

3. 条件语句

只要满足一定的条件,才会执行相应的语句。

3.1 if结构

if (boolean || 表达式) {
  code...
}

3.2 if...else 结构

if (boolean || 表达式) {
  code...
} else {
  code...
}

3.3 switch 结构

switch (fruit || 表达式) {
  case 'apple' || 表达式:
    code...
    break;
  case 'banana' || 表达式:
    code...
    break;
  default:
    code...
}

3.4 三元运算符

(条件)? 表达式1 : 表达式2

4. 循环语句

循环语句用于重复执行某个操作,它有多种形式。

4.1 while 循环

while (条件) {
  code...
}
var i = 0;
while(i < 100) {
  console.log('当前i是:' + i)
  i = i + 1
}

4.2 for 循环

var x = 3
for(var i = 0; i < x; i++) {
  console.log(i)
}

所有的 for 循环,都可以改写成 while 循环。

var x = 3
var i = 0
while (i < x) {
  console.log(i)
  i++
}

4.3 do...while 循环

do...while 循环 与 while 循环类似,唯一的区别就是先运行一次循环体,然后判断循环条件。

var x = 3
var i = 0
do {
  console.log(i)
  i++
} while (i < x)

不管条件是否为真,do...while循环体至少运行一次,这是这种结构最大的特点。

4.4 break 语句 和 continue 语句

break 语句用于跳出代码块或循环。

for (var i = 0; i < 6; i++) {
  console.log(i)
  if (i === 3) break;
}
// 打印出 0 1 2 3

continue 语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环。

var i = 0
while(i < 6) {
  i++
  if(i%2 === 0) continue 
  console.log(i)
}
// 打印出的全是奇数 1 3 5

JavaScript的数据类型

1. 简介

JavaScript语言的每一个值,都属于某一种数据类型。JavaScript的数据类型,共有八种。(ES6新增的 Symbol 和 BigInt 数据类型)

  • 数值(number):整数和小数
  • 字符串(string):文本
  • 布尔值(boolean):表示真伪的两个特殊值(true 和 false)
  • undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值
  • null:表示空值
  • Symbol:表示独一无二的值,对象的属性名可以使用Symbol()函数生成,可以保证不会与其他属性名产生冲突,这也是ES6引入Symbol的原因
  • 对象(object):各种值组成的集合

JavaScript的数据类型可以分为两大类:基本数据类型和复合数据类型。

基本数据类型是最简单的数据类型,它们是不可变的,这意味着它们的值不能被改变。当你对一个基本类型的值进行修改时,实际上是在复制一个新值。
JavaScript的基本数据类型包括:Number、String、Boolean、Undefined、Null、Symbol。

复合数据类型是由一个或多个值组成的数据类型,它们是可变的。JavaScript的复合数据类型:

  • Object:包括所有对象类型的集合,如普通对象、数组、函数、日期等
  • Function:在JavaScript中,函数也是对象

2. 数据类型的判断

在JavaScript中,可以使用 typeof 操作符 来获取基本数据类型的一个字符串描述。

但是 typeof  对于 null 返回 'object',这是一个常见的坑。

另外 typeof 不能区分 Object 和 Array 以及 null ,因为它们都返回 'object'。

typeof undefined // 'undefined'
typeof null // 'object'
typeof [1,2,3] // 'object'

instanceof 操作符可以判断一个变量是某个特定类型的实例。

const arr = []
const obj = {}
const fun = function() {}

arr instanceof Array; // true
obj instanceof Object; // true
fun instanceof Function; // true

3. null 和 undefined

null 和 undefiend 都可以表示"没有",含义非常相似。
将一个变量赋值给 undefined 和 null,老实说,几乎没有区别。

var a = undefined;
var a = null;

在 if 语句中,它们都会被自动转为 false 。相等运算符( == )甚至直接报告两者相等。

undefined == null // true

在 JavaScript 诞生时,最初像 Java 一样,只设置了 null 表示 "无"。根据 C 语言的传统, null 可以自动转为 0。

Number(null); // 0
5 + null; // 5

但是,JavaScript 的设计者 Brendan Eich,觉得这样做还不够。首先,第一版的 JavaScript 里面,null就像在 Java 里一样,被当成一个对象,Brendan Eich 觉得表示“无”的值最好不是对象。其次,那时的 JavaScript 不包括错误处理机制,Brendan Eich 觉得,如果null自动转为0,很不容易发现错误。

因此,他又设计了一个 undefined 。区别是这样的:

null 是一个表示 "空" 的对象,转为数值时为 0 。
undefined 是一个表示 "此处未定义" 的原始值,转为数值时为 NaN 。

Number(undefiend); // NaN
5 + undefined; // NaN

4. 数值

JavaScript内部,所有数字都是以64位浮点数的形式存储,即使整数也是如此。所以,11.0是相同的,是同一个数。

1 === 1.0; // true

4.1 与数值相关的全局方法

parseInt方法用于将字符串转为整数。

parseFloat方法用于将一个字符串转为浮点数。

parseInt('123'); // 123
parseFloat('3.14'); // 3.14

如果字符串有空格,空格将会自动去除。

parseInt(' 12 '); // 12
parseFloat(' 3.14 '); // 3.14

如果parseInt的参数不是字符串,将会先转为字符串再转换为整数。

parseInt(1.23);    
parseInt('1.23'); // 1

parseInt('8a'); // 8
parseInt('12**'); // 12
parseInt('15e3'); // 15
parseInt('5px'); // 5

parseFloat('3.14 Float'); // 3.14
parseFloat([1.23]);
parseFloat(String([1.23])); // 1.23

parseInt('abc'); // NaN
parseInt(''); // NaN
parseInt('.2'); // NaN
parseInt('+1'); // 1

parseFolat('FF'); // NaN
parseFloat(''); // NaN

parseInt方法的还可以接受第二个参数,表示被解析的进制(二、六、八、十),默认情况下,第二个参数为10,即默认为十进制。

parseInt('1000'); // 1000
parseInt('1000', 10); // 1000

parseInt('1000', 2); // 8
parseInt('1000', 6); // 216
parseInt('1000', 8); // 512

isNaN方法可以用来判断一个值是否为NaN

isNaN(NaN); // true
isNaN(123); // fasle

isNaN只对数值有效,如果传入其他值,会先被转为数值。

5. 字符串

在JavaScript中,字符串可以使用单引号也可以使用双引号。

var a = 'i am string';
var a = "i am string";

ES6引入了模板字符串

let name = 'ckn';
let templateString = `Hello, ${name}`;

5.1 字符串与数组

字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符。

var s = 'string';
s[0] // 's'
s[1] // 't'
s[2] // 'r'

但是,字符串与数组的相似性仅此而已。不能通过方括号运算符修改字符串中的单个字符

var s = 'string'
s[0] = 'a'
console.log(s); // 'string'

5.2  length 属性

length属性返回字符串的长度,该属性也是无法改变的。

var s = 'string';
s.length // 6
s.length = 3 // 不起任何作用 

5.3 字符串操作

JavaScript提供了多种字符串操作方法

  • 连接:使用 + 操作符或 concat() 方法

  • 切割:使用 slice() subString() subStr() 方法

  • 转换大小写: 使用 toUpperCase() 和 toLowerCase() 方法

  • 搜索和替换: 使用 indexOf() search() replace() 方法

  • 分割: 使用 split() 方法 (字符串转换为数组的方法)

    var str = 'hello, world'

    var replacedStr = str.replace('world', 'JS'); // 'hello, JS' var splitStr = str.split(','); // ['hello', 'world']

**注意:**字符串属于基本数据类型,所以字符串是不可变的,这意味着任何看似修改字符串的操作实际上都会生成一个新的字符串(replace和split方法后,str依旧是 'hello, world' 没有改变)

6. 对象

对象是JavaScript语言的核心概念,也是最重要的数据类型。

对象,简单来说就是一组 "键值对"(key- value)的集合,是一种无序的复合数据集合。

6.1 键名

对象所有的名字都是字符串(ES6又引入了Symbol值作为键名),所以加不加引号都可以。

var obj = {
  name: 'ckn',
  age: 18
}
var obj = {
  'name': 'ckn',
  'age': 18
}

如果键名不符合标志符名的条件(比如第一个字符为数字,或者有空格或运算符),则必须加上引号,否则会报错。

// 报错
var obj  = {
  1p: 'hello'
}
// 不报错
var obj = {
  '1p': 'hello',
  'h w': 'world',
  'a+b': 'hello, world'
}

对象的每一个键名又称为 "属性"(property),它的 "键值" 可以是任何数据类型。
如果一个属性的值为函数,通常把这个属性称为 "方法",它可以像函数那样调用。

6.2 对象的引用

如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中的一个变量,会影响到其他所有变量。

这种引用只局限于对象,如果两个变量指向同一个原始类型的值。
那么,变量这时都是值的拷贝。

6.3 属性的删除

delete命令用于删除对象的属性,删除成功后返回true

var obj = {
  a: 1
}
delete obj.p // true
obj.p; // undefined
Object.keys(obj); // []

6.4 属性的遍历: for...in 循环

for...in循环用来遍历一个对象的全部属性。

var obj = { a: 1, b: 2, c:3 }
for(var prop in obj) {
  console.log('键名key:', prop)
  console.log('键值value:', obj[prop])
}

for...in循环有两个使用注意点。

  • 它遍历的是对象所有可遍历(可枚举 enumberable)的属性
  • 它不仅遍历对象自身的属性,还遍历继承来的属性

一般情况下,都是只想遍历对象自身的属性,所以使用for...in的时候,应该结合使用hasOwnProperty方法,在循环内部判断一下,某个属性是否是对象自身的属性。

function propIsOwnSelf(obj, prop) {
  return obj.hasOwnProperty(prop) && (prop in obj)
}

7. 函数

7.1  函数的声明

JavaScript 有三种声明函数的方法

(1) 函数声明

function命令声明的代码区块,就是一个函数。

function print(s) {
  console.log(s)
}

(2) 函数表达式

这种写法将一个匿名函数赋值给变量。这时,这个匿名函数又称函数表达式,因为赋值语句的等号右侧只能放表达式。

var print = function(s) {
  console.log(s)
}

(3) Function 构造函数

下面定义函数的方式是使用 Function 构造函数,它可以接受任意数量的参数,但最后一个参数始终都被看成是函数体。

从技术角度来讲,这也是一个函数表达式。这种语法对于理解 "函数是对象,函数名是指针" 的概念倒是非常直观的。

var add = new Function(
  'a',
  'b',
  'return a + b'
)
// 等同于
function add(a, b) {
  return a + b
}

7.2  第一等公民

JavaScript 语言将函数看作一种值,与其他值地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的返回值返回。

综上:函数只是一个可以执行的值,此外并无特殊之处。

由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民,这也为函数式编程奠定了基础。

7.3  函数名的提升

JavaScript 引擎将 函数名 视同 变量名,所以采用 function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。

7.4  函数的属性和方法

函数的name属性返回函数的名字。

function f1() {}
f1.name // f1

var f2 = function () {}
f2.name // f2

var f3 = function myName() {}
f3.name // myName

name属性的一个用处,就是获取参数函数的名字。

var foo = function() {}
function f(fun) {
  console.log(fun.name) // foo
}

函数的length属性返回函数预期传入的参数个数,即函数定义之中的参数个数。

function f(a,b) {}
f.length // 2

上面代码定义了空函数f,它的length属性就是定义时的参数个数。
不管调用时传入了多少个参数,length属性始终等于2。

length属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的“方法重载”(overload)。

函数的toString()方法返回一个字符串,内容是函数的源码。

对于那些原生的函数,toString()方法返回function (){[native code]}

function f() {}
f.toString() // 'funcion f() {}'

Math.sqrt.toString()
// 'function sqrt() { [native code] }'

7.4 函数作用域

作用域指是指变量和函数的可见性和可访问性的范围,作用域决定了在何处可以访问到其他或特定的变量和函数。(通俗的理解:在一定范围内,变量和函数能否被访问,以及能否访问其他的变量和函数)。

在ES5的规范中,JavaScript只有两种作用域:

  • 全局作用域
  • 函数作用域

对于顶层函数来说,函数外部声明的变量就是全局变量,它可以在函数内部读取。
在函数内部定义的变量,外部无法读取,称为局部变量。

var a = 1function f() {
  var b = 2
  console.log(a) // 1
}
console.log(b)

7.5 函数本身的作用域

函数本身也是一个值(就像一个变量一样,只不过该变量指向的是一个函数),也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与运行时(调用时)所在的作用域无关。

var a = 1
var x = function () { console.log(a) }
function f() {
  var a = 2
  x()
}
f() // 1

总之,函数执行时所在的作用域,是声明时的所用域,而不是调用时的作用域。

7.6 arguments 对象

由于 JavaScript 允许函数有不定数目的参数,所有需要一种机制,可以在函数体内部读取所有参数,这就是 arguments 对象的由来。

arguments对象有 length 属性,可以判断函数调用时带了几个参数。

它是类数组对象,不是真正的数组对象。虽然arguments很像数组,但它是一个对象。数组专有的方法(比如sliceforEach),不能在arguments对象上直接使用。

var args = Array.prototype.slice.call(arguments)

var args = []
for(var i = 0; i < arguments.length; i++) {
  args.push(arguments[i])
}

arguments对象带有一个callee属性,返回它所对应的原函数,也就是自身。

function f() {
  console.log(arguments.callee === f) // true
}

8. 数组

数组(array)是按次序排列的一组值。同样它的值像对象的属性一样,可以是任意数据类型。

本质上,数组属于一种特殊的对象。typeof运算符会返回数组的类型是object

var arr = ['a', 'b', 'c']
typeof arr // 'object'
Object.keys(arr) // ['0', '1', '2']