JavaScript 进阶-两大编程模式

一、背景

JavaScript 是一门容易入门但难以进阶的语言,开发者很容易就能够用 JavaScript 编写程序,但是当应用的规模越来越大,副作用越多,程序也越来越不容易维护。

在根本上来说,是因为开发者还没有深入的学习 JavaScript,就匆匆的投入到开发工作中去了。

我计划将 JavaScript 进阶的知识进行整理,本文首先介绍的是 JavaScript 编程模式。

JavaScript 是一个多模式的语言,支持声明式和指令式两种模式。

在 JavaScript 所支持的编程模式中,用得最多的是面向对象(OOP object oriented programming)和函数式(FP functional programming)两种。

二、函数式编程

1. 函数是什么、如何使用?

一个函数由输入、函数和输出组成。

如下是一个计算加法的函数:

function sum(a, b) {
  return a + b;
}
calculateGST(1, 2); // return 3

函数本身作为对象,也可以是输入或输出值,我们把这种函数就叫做高阶函数。

2. 函数中都有哪些副作用?

在函数式编程中,我们通常会把各种干扰,就叫做副作用。

2.1 全局变量

函数中最常见的第一类副作用,就是全局变量。

当函数中引入了全局变量时,我们没法保证外部函数没有改变全局变量的值,也没法保证每次输出的结果是 1。所以从输入开始,这种不确定性就存在了。

var x = 1;
foo();
console.log(x);
bar();
console.log(x);
baz();
console.log(x);

2.2 IO 影响

函数中最常见的第二类副作用,就是 IO 影响。

IO 类似前端浏览器中的用户行为,比如鼠标和键盘的输入,或者如果是服务器端的 Node 的话,就是文件系统、网络连接以及 stream 的 stdin(标准输入)和 stdout(标准输出)。

2.3 网络请求

函数中最常见的第三类副作用,是与网络请求相关。

比如我们要针对一个用户下单的动作发起一个网络请求,需要先获得用户 ID,再连着用户的 ID 一起发送。如果我们还没获取到用户 ID,就发起下单请求,可能就会收到报错。

3. 减少副作用:纯函数和不可变

要如何减少以上这些副作用呢?

首先要了解的是两个核心概念:纯函数和不可变。

3.1 纯函数

纯函数的意思是说,一个函数的返回结果的变化只依赖其参数,并且执行过程中没有副作用。

比如上面介绍的加法函数就是一个纯函数,它的返回结果只依赖参数 a 和参数 b,没有其他因素可以改变返回结果。

function sum(a, b) {
  return a + b;
}
calculateGST(1, 2); // return 3

3.2 不可变

不可变的意思是,不要去改变参数,尤其是引用类型,这样我们可以避免函数对外部的副作用。

三、面向对象编程

1. 对象是什么、如何创建?

什么是对象,可以说是万物皆对象,比如我们可以这样创建一个鸭子对象:

const ya = {
  name: "鸭子",
  getName: function () {
    return this.name;
  },
};

console.log(ya.getName()); // 返回 "鸭子"

2. 为什么需要封装、重用和继承、组合?

2.1 封装

封装就是把一些强耦合的逻辑放在一起,比如封装组件、方法。

2.2 重用

重用就是把封装的组件、方法进行复用。

2.3 继承

继承就是将一些特定的行为或属性放在实现类中,这样在继承了基础的父类功能的基础上,我们能够在子类中作一些改动。

2.4 组合

当一个类的父子的层级过于复杂,这时,如果父类有了问题,就会牵一发动全身,而且抽象的层级过多,也会让代码难以理解。

可以通过组合来解决这个问题。

组合就是一个子类不是继承的某个父类,而是通过组合多个类,来形成一个类。

3. 什么是基于原型的继承?

在传统的面向对象语言,比如 Java 里,当我们用到继承时,一个类的属性和功能是可以被基于这个类创建的对象“拷贝”过去的。

但是在 JavaScript 里,虽然我们用到继承时,并没有将其属性和功能拷贝过来,而是默认通过原型链来寻找原型中的功能,然后利用“链接”而不是“拷贝”来。

下面是一个原型链继承示例:

function Animal(name) {
  this.name = name;
}

Animal.prototype.identify = function () {
  return "这是" + this.name;
};

function Duck(name) {
  Animal.call(this, name);
}

Duck.prototype = Object.create(Animal.prototype);

Duck.prototype.display = function () {
  console.log("你好, " + this.identify() + ".");
};

var duck1 = new Duck("鸭子1");
var duck2 = new Duck("鸭子2");

Object.getPrototypeOf(notice1) === Duck.prototype; //true
Object.getPrototypeOf(notice2) === Duck.prototype; //true

duck1.display(); // "你好,这是鸭子1"
duck2.display(); // "你好,这是鸭子2"

上面的代码中,我们是通过函数自带的 call() 方法和对象自带的 Object.create() 方法,让 Duck 作为子类继承了 Animal 父类的属性和方法,然后我们创建了两个实例 duck1 和 duck2。

而这时,我们如果用 getPrototypeOf 来获取 duck1 和 duck2 的原型,会发现它们是等于 Duck 原型。

当我们用 display 方法调用这个方法时,实际调用的是原型链里 Duck 的原型中的方法。

小结

本文,我们主要了解了函数式编程和面向对象编程的核心概念。

函数式编程是管理和解决副作用,面向对象编程是服务于业务对象。

深入理解编程模式能为我们学好并更好地应用 JavaScript 这门语言,提供扎实的理论基础。

参考资料

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容