JavaScript到底是什么玩意儿?

430 阅读9分钟

摆在明面上的解释

Javascript到底是什么? 有很多的官方解释,我们不妨先列出来看一下:

  1. 讨论JS功能不能脱离JS的运行环境
  2. JS基于原型设计
  3. JS是一门脚本语言(动态语言)
  4. JS是单线程语言
  5. Java和Javascript是两种不同的语言

紧接着我们逐个来讨论。


  1. 谈论JS功能不能脱离JS的运行环境

可以说,JS+运行时环境才构成了完整的JS功能模块。

目前来说,JS的运行时环境主要有两个,一个是浏览器环境,另外一个就是Node环境。

JS本身:
  • 语法:变量声明、数据类型、操作符、控制结构、函数定义、闭包、类和继承
  • 对象和原型链:对象字面量、构造函数、原型、属性访问器
  • 异常处理:try...catch...finally、throw
  • 异步编程:回调函数、Promise、async/await、定时器
  • 事件循环和任务队列:事件循环、微任务、宏任务
  • 内置对象:基本类型对象、全局对象
  • ECMAScript 标准:遵循ECMAScript标准的语言特性
浏览器环境:
  • DOM 文档对象模型 API:DOM API 让 JavaScript 能够直接操纵网页的元素,改变结构、调整样式或更新内容,原生JS中常见比如document
  • BOM (浏览器对象模型) API:BOM API 允许 JavaScript 与浏览器窗口互动,比如调整窗口大小、浏览历史记录等等,比如loaction、history等等
  • 网络请求 API:通过 XMLHttpRequest 和 Fetch API,JavaScript 可以发送和接收网络请求
  • Web存储 API:localStorage 和 sessionStorage API 提供了在浏览器中保存数据的能力
  • Canvas和WebGL API:这些API让 JavaScript 能够在网页上绘制图形,比如3D、2D效果等
  • Web Workers API:Web Workers 允许 JavaScript 在后台线程运行代码,避免阻塞主线程
Node环境:
  • 非阻塞 I/O:Node.js 的非阻塞I/O模型让它能够高效处理大量并发操作
  • CommonJS 模块系统:require 和 module.exports等
  • 全局对象global:global 对象类似于 Node.js 的心脏,提供对全局作用域的访问,相对于浏览器提供的Window全局对象
  • 文件系统( fs API:fs API 让 Node.js 能够读写文件
  • 网络(net)和HTTP/ HTTPS ****API:这些API允许 Node.js 创建服务器和客户端,进行网络通信。
  • 操作系统 级别的 API:process 对象提供了访问操作系统级别的功能,如命令行参数、环境变量等
  • npm 包管理工具:npm 是 Node.js 的包管理器,它让安装和管理第三方库变得简单
  • 事件循环和事件驱动编程:Node.js 的事件循环机制和事件驱动编程模型,让它能够以非阻塞的方式处理事件
  • 流(Stream) API:流API允许 Node.js 以流的形式处理数据

关于JS及其运行时提供的API有很多,我们可以后期展开讲讲。


  1. JS基于原型设计

JavaScript 的原型设计是基于原型继承的编程范式,这是一种不同于传统类继承的继承模型。以下是对 JavaScript 基于原型设计的解释:

原型对象(Prototype Object)

在 JavaScript 中,每个对象都有一个原型(prototype)对象,这个原型对象可以作为一个模板,为新创建的对象提供属性和方法。当访问一个对象的属性或方法时,如果这个对象本身没有这个属性或方法,解释器就会去它的原型对象中查找。

函数和原型

在 JavaScript 中,函数(Function)是一个特殊的对象,它有一个 prototype 属性,这个属性指向一个原型对象。当使用 new 关键字创建一个函数的实例时,这个实例的内部原型(__proto__[[Prototype]])会被设置为该函数的 prototype 属性。

原型链(Prototype Chain)

原型链是一个由原型对象组成的链,它定义了属性的继承关系。当访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,解释器就会沿着原型链向上查找,直到找到这个属性或方法,或者到达原型链的顶端(通常是 Object.prototype)。

如何解释基于原型设计
  1. 对象创建:在 JavaScript 中,对象是通过函数(构造函数)创建的,而不是通过类。当你创建一个新对象时,你可以指定它的原型。
  2. 继承:JavaScript 中的继承是通过原型链实现的。一个对象可以继承另一个对象的属性和方法,这通过将一个对象设置为另一个对象的原型来实现。
  3. 共享属性和方法:原型对象上的属性和方法可以被所有继承自该原型的对象共享。这意味着,如果你在原型上添加一个方法,所有继承自该原型的对象都可以使用这个方法。
  4. 动态性:基于原型的继承模型非常灵活,因为你可以随时添加、修改或删除原型对象上的属性和方法,所有继承自该原型的对象都会立即看到这些变化。

以下是一个简单的例子来说明基于原型设计:

// 构造函数
function Animal(name) {
  this.name = name;
}
// 在原型上添加一个方法
Animal.prototype.sayName = function() {
  console.log(this.name);
};
// 创建一个实例
var myAnimal = new Animal('cell');
// 调用原型上的方法
myAnimal.sayName(); // 输出 'cell'

在这个例子中,Animal 函数的 prototype 属性指向一个原型对象,该原型对象有一个 sayName 方法。当 myAnimal 对象调用 sayName 方法时,它实际上是从原型对象那里继承来的。

关于基于原型还是对象的争议

我的理解是,既然会有“Js是基于原型还是对象设计?”的争议,我最开始也有这个疑惑,但是如果从不同角度思考,或许就没那么纠结了,JS同时基于原型又基于对象。

基于原型的 继承

 // 构造函数 Animal
function Animal(name) {
    this.name = name;
}

// 原型方法
Animal.prototype.sayName = function() {
    console.log(this.name);
};

// 构造函数 Dog,继承自 Animal
function Dog(name, breed) {
    Animal.call(this, name); // 调用 Animal 的构造函数this.breed = breed;
}

// 设置 Dog 的原型为 Animal 的原型
Dog.prototype = Object.create(Animal.prototype);

// 添加 Dog 特有的方法
Dog.prototype.bark = function() {
    console.log('Woof!');
};

// 创建 Dog 实例
var myDog = new Dog('Buddy', 'Golden Retriever');

// 调用继承的方法
myDog.sayName(); // 输出 'Buddy'
myDog.bark(); // 输出 'Woof!' 

在这个例子中,我们创建了一个 Animal 构造函数和一个 Dog 构造函数。Dog 继承了 Animal 的属性 name 和方法 sayName。通过设置 Dog.prototype = Object.create(Animal.prototype),我们创建了一个新的原型链,使得 Dog 实例可以访问 Animal 的原型方法。

基于对象的特性:

 // 创建一个对象
var person = {
    name: 'John',
    greet: function() {
        console.log('Hello, my name is ' + this.name);
    }
};

// 访问对象的属性
console.log(person.name);  // 输出 'John'

// 调用对象的方法
person.greet(); // 输出 'Hello, my name is John' 

在这个例子中,我们创建了一个 person 对象,它有一个 name 属性和一个 greet 方法。我们通过对象字面量直接创建了这个对象,并且可以直接访问和修改它的属性。这展示了 JavaScript 中对象作为数据结构的基本特性。

结合两种特性:

 // 创建一个构造函数
Personfunction Person(name) {
    this.name = name;
}

// 原型方法
Person.prototype.greet = function() {
    console.log('Hello, my name is ' + this.name);
};

// 创建一个对象 person
var john = new Person('John');

// 调用原型方法
john.greet(); // 输出 'Hello, my name is John' 

在这个例子中,我们结合了基于原型的继承和基于对象的特性。Person 构造函数创建了一个新对象 john,并且 john 可以访问 Person 原型上的 greet 方法。

  • 不同视角:开发者或者文献可能更关注Js的特定方面开描述JS,笔者最初也是只用对象,当时便把JS认为基于对象设计。
  • JS 的双重性:JS的设计具有双重性,比如基于原想的继承模型,又有以对象为中心的模型。
  1. JS是一门单线程语言

那就不得不提到Js的并发模型和时间循环机制

关于Js的事件循环机制有很多可以讲解,但我们在这里篇幅原因,浅谈即可。

事件循环又叫做消息循环,是浏览器渲染主线程的工作方式。在 chrome V8引擎的源码中,它开启一个不会结束的 for 循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时候将任务加入到队列未尾即可。过去把消息队列简单分为宏队列和微队列,这种说法目前已无法满足复杂的浏览器环境,取而代之的是一种更加灵活多变的处理方式。根据 W3C 官方的解释,每个任务有不同的类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪一个队列的任务。但浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先调度执行。

永不阻塞

虽然Js是一门单线程的语言,它不像Cpp这种可以开启多线程来运行程序,但是因为JS有着出色的异步机制,在异步机制的帮助下,也实现了永不阻塞,甚至I/O性能更优。

  1. Js是一种脚本语言

“脚本”这两个字貌似听起来很低级,但如果换个说法“动态”语言是否听起来就高级起来了呢?

常见的动态(脚本)语言有很多,比如当今在人工智能领域一家独大的Python、老牌万能语言PHP,当然还有我们熟知的Javascript。

不像C这种每次写完的代码总要编译并且生成机器码文件才可以执行,脚本语言通常被设计为解释执行,而不是编译成机器码。这就很动态!

但Js也可以被“编译执行”,比如为了兼顾老旧版本浏览器等,JS通常被Babel编译转换成es5版本。