1.介绍
课程背景 :
1.前端的主要编程语言为 JavaScript。
2.JavaScript 做为一种融合了多种编程范式的语言,灵活性非常高
3.前端开发人员需要根据场景在不同编程范式间自如切换。
4.进一步需要创造领域特定语言抽象业务问题。
2.编程语言
为什么需要编程语言
从人与人间流的语言类比推出人与机器的语言
机器语言
不同的机器有不同的格式
例如8086指令格式分操作码与操作数
汇编语言
高级语言
为适应人的思维
接着出现编译器,将高级语言编译成汇编语言,再转变成机器语言
结果就是很小的源码编译出很大的机器语言
C/C++
C语言:面向过程的突出代表
可对位,字节,地址直接操作
代码和数据分离倡导结构化编程
功能齐全:数据类型和控制逻辑多样化
可移植能力强
C++:面向对象语言代表
C with Classes
继承
权限控制
虚函数
多态
//下面为一个包括全知识点的C++程序
#include<iostream>
#include<string>
using namespace std;//std空间声明
class Shape//抽象类
{
public:
virtual float area() const { return 0.0; };
virtual float volume() const { return 0.0; };
virtual void shapeName() const = 0;//纯虚函数
};
class Point :public Shape//公有继承
{
public:
Point(float a= 0, float b= 0)//构造函数
{
x = a, y = b;
}
void setPoint(float a, float b)
{
x = a, y = b;
}
float getX() const { return x; }
float getY() const { return y; }
virtual void shapeName() const { cout << "Point:"; }
friend ostream& operator<<(ostream& output, const Point& p);//声明、友元函数实现运算符重载
protected:
float x, y;
};
ostream& operator<<(ostream& output, const Point& p)//类外实现功能
{
output << "[" << p.x << "," << p.y << "]" << endl;
return output;
}
class Circle:public Point//继承
{
public:
Circle(float x = 0, float y = 0, float r = 0) :Point(x, y), radius(r){ }
void setRadius(float a) { radius = a; }//参数列表构造
float getRadius() const { return radius; }
virtual float area() const { return 3.1415926 * radius * radius; }//返回面积
virtual void shapeName() const { cout << "Circle:"; }
friend ostream& operator<< (ostream&, const Circle&);//运算符重载
protected:
float radius;//自己的私有成员
};
ostream& operator<< (ostream& output, const Circle& c)
{
output << "[" << c.x << "," << c.y << "],r=" <<c.radius<< endl;
return output;
}
class Cylinder:public Circle//继承
{
public:
Cylinder(float x = 0, float y = 0, float r = 0, float h = 0) :Circle(x, y, r), hight(h) { }
void setHight(float h) { hight = h; }
virtual float area() const { return 2 * Circle::area() + 2 * 3.1415926 * radius * hight; }
virtual float volume() const { return Circle::area() * hight; }
virtual void shapeName() const { cout << "Cylinder:"; }
friend ostream& operator<< (ostream&, const Cylinder&);
protected:
float hight;
};
ostream& operator<< (ostream& output, const Cylinder& cy)
{
output << "[" << cy.x << "," << cy.y << "],r=" <<cy.radius<<",h="<<cy.hight<< endl;
return output;
}
int main()
{
Point point(3.2, 4.5);
Circle circle(2.4, 1.2, 5.6);
Cylinder cylinder(3.5, 6.4, 5.2, 10.5);
point.shapeName();
cout << point;
circle.shapeName();
cout << circle;
cylinder.shapeName();
cout << cylinder;
cout << endl;
Shape* p;//一个基类指针,能指向派生类
p = &point;
p->shapeName();
cout << "x=" << point.getX() << ",y=" << point.getY() << "\narea="
<< point.area() << "\nvolume=" << p->volume() << endl<< endl;
p = &circle;
p->shapeName();
cout << "x=" << circle.getX() << ",y=" << circle.getY() << "\narea="
<< circle.area() << "\nvolume=" << p->volume() << endl<<endl;
p = &cylinder;
p->shapeName();
cout << "x=" << cylinder.getX() << ",y=" << cylinder.getY() << "\narea="
<< cylinder.area() << "\nvolume=" << p->volume() <<endl<< endl;
return 0;
}
Lisp:函数式语言代表
与机器无关
列表:代码即数据
闭包
(set nums `(1 2 3 4));数据列表
(setq add `+);加操作
(defun run(op exp) (eval (cons op exp)));将数据构建成代码列表
(run add nums);运行
JavaScript
-
基于原型和头等函数的多范式语言
-
过程式
-
面向对象
-
函数式
-
响应式
此外,还有
弱类型:JavaScript是一种弱类型的语言,不需要事先声明变量的类型,变量的类型会在运行时自动推断(在TS中变成强类型)。
解释性:JavaScript是一种解释性的语言,不需要编译成可执行文件,可以直接在浏览器中执行(经典的例如V8引擎会进行处理)。
高阶函数:JavaScript中的函数可以作为参数传递给其他函数,也可以作为返回值返回给其他函数,这种函数称为高阶函数。
闭包:JavaScript中的函数可以形成闭包,即在函数内部定义的变量可以在函数外部访问,这种特性可以实现私有变量和函数的封装。
3.编程范式
什么是编程范式
命令式侧重程序员如何操作机器改变状态,
过程式是把操作用过程分组
面向对象是根据操作和它所面向的对象来调整
过程式编程
-
自顶向下
-
结构化编程--替代传统goto结构
- 顺序结构
- 选择结构
- 循环结构
JS中的面向过程
export var car = {
meter:100,
speed:10,
};#数据
export function advanceCar(meter){
while (car < meter){
car.meter +=car.speed;
}#算法
}
import {car,advanceCar }from './car';
function main(){
console.log('before',car);
advanceCar(1000);
console.log('after',car);
}
面向过程问题
- 数据与算法关联弱
- 不利于修改和扩充
- 不利于代码重用
也就是不利于拓展和维护、代码重用
封装
- 关联数据与算法
class Car {
meter = 100
speed = 100
advance(meter){#数据与算法封装到一个类中去
while(this.meter<meter){
this.meter+=this.speed;
}
}
getSpec(){
return `meter:${this.meter};speed:${this.speed};`;
}
}
function main() {
var car = new Car();
car.advance(1000);
}
继承
class FlyCar extends Car{
height = 100 #新数据、新算法,可在在原数据上直接扩充
fly(height){
while (this.height < height){
this.height += this.speed
}
}
}
多态
不同的结构可以进行接口共享、进而达到函数复用
#反复使用接口,公共的类可以使用相同的接口
getSpec(){
return `meter:${this.meter};speed:${this.speed};`;
}
}
依赖注入
去除代码耦合
class Car{
engine = new Engine()
wheel =new Wheel()//耦合特定实现
run(){
this.engine.strat();
this.wheel.run();
}
}
function main(){
var car = new Car();
car.run();
}
面向对象编程_五大原则
- 单一职责原则SRP(Single Responsibility Principle)
- 开放封闭原则OCP(Open-Close Principle)
- 里式替换原则LSP(the Liskov Substitution Principle LSP)
- 依赖倒置原则DIP(the Dependency Inversion Principle DIP)
- 接口分离原则ISP(the Interface Segregation Principle ISP)
面向对象编程有什么缺点?为什么我们推荐函数式编程?
函数式编程
- 函数是"一等公民"
- 纯函数/无副作用
- 高阶函数跟闭包
优势
- 可缓存
- 可移植
- 可测试
- 可推理
- 可并行
const retireAge = 60
function retirePerson(p){
if(p.age > retireAge){
p.status = "retired"
}
}
//优化
function retirePerson(p){
const retireAge = 60
if(p.age > retireAge){
return {
...p,
status = "retired"
}
}
return p
}
Currying
- 基本概念
在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
下面我们通过一些实例,拆解和说明一下柯理化的具体含义。
这是一个普通的三元函数,执行运算得出三个参数的和。
function add(a,b,c){
return a + b + c
}
add(1,2,3) // 6
按照上面的定义,add函数的的柯理化转换过程应该是add(a,b,c) => curriedAdd(a)(b)(c)。实现代码如下:
function curriedAdd(a){
return function(b){
return function(c){
return a + b + c
}
}
}
执行验证
add(1,2,3) // 6
curriedAdd(1)(2)(3) // 6
上面是基于极简场景的实现
Currying的实现也是利用闭包和高阶函数,把之前的参数暂存起来,同时返回一个新的函数。
Composition
- 在计算机科学中,函数组合是将多个简单的函数,组合成一个更复杂的函数的行为或机制。每个函数的执行结果,作为参数传递给下一个函数,最后一个函数的执行结果就是整个函数的结果。
鼓励组合
可以手动组合,但灵活性差
目标用途
- 避免洋葱嵌套代码,提高代码的可读性
- 组合多个单一功能函数生成特定功能的新函数
- 把功能复杂的函数拆解成功能相对单一的函数,便于维护和复用
结合律(Associativity)
在数学中,意指在一个包含有二个以上的可结合运算子的表示式,只要算子的位置没有改变,其运算的顺序就不会对运算出来的值有影响。
加法和乘法都是可结合的:
(x * y) * z === x * (y * z)
(x + y) + z === x + (y + z)
函数组合遵循结合律,意指只要函数的位置没有发生改变,任意组合优先执行,都不影响执行结果。
compose(f,g,h) == compose(compose(f, g), h) == compose(f, compose(g, h))
Functor
- 可以当做容器的类型,类型支持对容器内元素进行操作
- 常见的:functor:Array(Iterable).map,Promise.then
a.b != null ? (a.b.c != null ?(a.b.c.d !== a.b.c.d.e :null) : null) :null
new Maybe(a).map(Prop('b')).map(prop('c')).map(prop('d')).map(prop('e'));
Monad
Monad 由以下三个部分组成:
- 一个类型构造函数(M),可以构建出一元类型
M<T>。 - 一个类型转换函数(return or unit),能够把一个原始值装进 M 中。
- unit(x) :
T -> M T
- unit(x) :
- 一个组合函数 bind,能够把 M 实例中的值取出来,放入一个函数中去执行,最终得到一个新的 M 实例。
M<T>执行T-> M<U>生成M<U>
除此之外,它还遵守一些规则:
- 单位元规则,通常由 unit 函数去实现。
- 结合律规则,通常由 bind 函数去实现。
单位元:是集合里的一种特别的元素,与该集合里的二元运算有关。当单位元和其他元素结合时,并不会改变那些元素。
乘法的单位元就是 1,任何数 x 1 = 任何数本身、1 x 任何数 = 任何数本身。
加法的单位元就是 0,任何数 + 0 = 任何数本身、0 + 任何数 = 任何数本身。
Applicative
直接对两个容器操作
- 函子的值可以是函数,我们把value值是函数的函子称之为可应用的函子。但是这样理解不完整,因为我们看不到它的”可应用性“体现在哪里?函数(function)可以被应用,除了可以直接调用,它还有一个apply方法,可以把函数应用于其它对象。
[].map.apply([1,2,3],[n=>n*2]) // [2,4,6]
Identity: Maybe(id).ap(v) === v;
Homomorphism: Maybe(f).ap(Maybe(x)) === Maybe(f(x));
Interchange: v.ap(Maybe(x)) === Maybe(f => f(x)).ap(v);
Composition: Maybe(compose).ap(u).ap(v).ap(w) === u.ap(v.ap(w));
响应式编程
- 异步/离散的函数式编程
- 数据流
- 操作符
- 过滤
- 合并
- 转化
- 高阶
Observable
- 观察者模式
- 迭代器模式
- Promise和EventTraget超集*
操作符
响应式编程的"compose"
- 合并
- 过滤
- 转化
- 异常处理
- 多播
Monad
- 去除嵌套的Observable
领域特定语言
什么是领域特定语言
-
领域特定语言 (英语:domain-specific language、DSL)指的是专注于某个 应用程序 领域的 计算机语言 。
- HTML
- SQL
-
与之对应的是通用语言
- C/C++
- Javascript
- ...
特定语言需要由通用语言实现,通用语言无法由特定语言实现
语言运行
lexer
SQL Token分类
- 注释
- 关键字
- 操作符
- 空格
- 字符串
- 变量
Parser_语法规则
上下文无关语法规则
<selectStatement> ::= SELECT <selectList> FROM <tableName>
<selectList> ::= <selectField> [ ,<selectList> ]
<tableName> ::= <tableName> [ ,<tableList> ]
推导式:表示非终结符到(非终结符或终结符)的关系。 终结符:构成句子的实际内容。可以简单理解为词法分析中的token. 非终结符:符号或变量的有限集合。它们表示在句子中不同类型的短语或子句。
Parser_LL
LL:从左到右检查,从左到右构建语法树
function selectStatement(node) {
node.children.push(matchToken( "select'));
const selectListNode = { type: 'selectList', children: []};
node.children.push(selectListNode);
selectList(selectListNode);
node.children.push(matchToken( 'from"));
node.children.push(tableNasme());
}
function selectList({ children }) {
let node;
while (node = selectField()) {
children.push(node);
}
function selectField() {
if (currentToken().match(/\w+/)) {
return { type: 'field", token: nextToken() };
}
}
Parser_LR
LR:从左到右检查,从右到左构建语法树
LL(K) > LR(1) > LL(1),括号里的内容构建语法树需要向下看的数量