最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • js中的面向对象编程和函数式编程

    正文概述 掘金(卖油条的。)   2021-02-25   731

    本文会结合两种常见的编程范式,对js语言层面的一些特性进行复习,并对Composing Software中的观点加以理解。


    编程范式

    前文我们讨论了编程范式,这里稍微回顾一下:

    在我们编程中每解决一个问题对应concept,每种paradigm是多个concepts的组合,而每种编程语言实现了一种或多种paradigms。

    常见concept组合

    • [record] 当只有记录(record)概念,被称为描述式声明编程(descriptive declarative programming),比如xml
    • [record,procedure] 当添加上过程(procedure)被称为一等函数式编程(first-order functional programming)
    • [record,procedure,cell(state)] 当添加上使用cell传递数据,则被称为命令式编程(imperative programming),比如C语言,cell在这里代表一种类似细胞之间的信息传递方式,state表示状态。
    • [record,procedure,cell(state),closure] 当添加上闭包(closure)就可以称为顺序面向对象编程(sequential object-oriented programming) 或有状态的函数式编程(stateful functional programming),比如java,当加上thread后就有了并发的能力,这里暂且不提。
    • [record,procedure,closure] 当OOP基础上减去状态,就成了函数式编程(functional programming),比如scheme,js就是参考了scheme中很多特性

    面向对象和函数式

    OOP和FP是两种比较常见的编程范式,也被js同时支持,相关讨论有很多,这里只代表其中一种看法,更详细的会在后面章节介绍。

    核心

    按照前文说法,两种范式的区别就是FP比OOP少了一个state concept,即

    面向对象侧重于对数据的各种处理,数据是对象的内部状态, C++ Primer Plus 对对象的本质表述为用来设计并扩展自己的数据结构:

    函数式侧重于数据流的流动,数据是外界的输入,并最终将对应结果返回,A Brief Intro to Functional Programming 对于函数式编程概括为一种数据流,而不是控制流

    适用场景

    以下结论参考Functional programming vs Object Oriented programming [closed]

    • 当对事物有固定操作时选择面向对象,主要用来添加新的事物,可以通过添加新类实现现存的方法来完成,存在的问题是当需要添加新操作时需要编辑很多类。 在项目中的例子比如对下载器的封装,每种下载器都有开始、暂停、恢复等固定动作。
    • 当事物本身固定时选择函数式编程,主要是对现有事物添加操作,可以通过添加函数处理当前数据类型,存在的问题是当需要添加新事物时,需要编辑很多参与处理的函数。最直接的例子就是编译器,输入源代码,通过一系列操作,输出目标代码;再比如webpack中的loader。

    面向对象专家 Michael Feathers 也说过:

    面向对象的可读性是通过封装各部分,函数式的可读性是通过最小化各部分,即前者将数据和数据的动作进行封装,后者将各种操作拆分以最小化。

    在日常开发怎么选择

    每一个应用都是由各种组件组合而成,只不过组件的形式不固定,比如functions,data structures,classes等。
    不同编程语言倾向于使用不同的原子元素组成组件,比如java使用对象,haskell使用function等。而在js中,因为天然支持面向对象和函数式,因此在项目中往往混合使用,我们可以使用对象组合来为函数式编程生成数据类型,使用函数式编程为面向对象编程生成对象,不管怎么写,软件开发的本质就是组合。

    我们的工作就是根据各种情况使用各种编程范式,像盖房子一样将各种组件组合起来,在具体讨论两种范式之前我们先多了解一下js本身,

    js中的相关特性

    我们需要先对js这种语言中的各种特性有所了解,才能利用范式这种工具更好的对我们开发的软件进行组合。 这一部分主要会参考ecma262。

    js语言的设计

    Brendan Eich开发了js。他在这篇文章提到了js设计过程中的一些问题。
    Netscape需要在浏览器中内置一种脚本语言,根据领导要求,首先需要像java(Look like Java,因此很多语法和java类似),而作者本人偏向于scheme,因此最后在新的语言中选择了和Scheme一样的一等函数以及和Self一样的prototype作为主要组成。受java影响,有了原始类型和对象的区分,比如string和String。

    以上设计加上后来的发展就成了现在的js。

    js特性概述

    在js中的数据结构分为两种,原始类型和对象,js的对象创建并不是基于class的,而是有很多方式,比如字面量或者构造函数,每个构造函数都有一个prototype属性用于实现基于原型(prototype-based)的继承。一个构造函数的prototype还有一个constructor引用指向构造函数本身,当实现继承时,这个属性可能会改,按照惯例需要修正,但不是必须的(关于constructor参考这里)。

    说起构造函数,这里补充一点相关概念,函数是一种特殊的对象,含有internal method [[Call]],因此可以通过函数调用来执行相关代码,而构造函数又是一种特殊的函数,含有internal method [[Construct]],可以通过newsuper调用创建对象。

    js中的对象

    原型链

    每一个通过构造函数创建的对象都有一个指向构造函数prototype属性的隐式引用(可以用__proto__访问但不推荐),而这个prototype本身可能也有一个非null的引用指向它的prototype,等等,这就被称为原型链。当访问对象的一个属性时,会首先从该对象本身查找,如果找不到就会沿着原型链依次查找,直到找到或者找到尽头发现没有,原型链上的属性可以被覆盖。

    相较基于class继承的语言,通常来说,状态被实例拥有,方法被class拥有,继承的只有结构和行为(behavior),而js这一切都是可以继承的。这里的行为是方法整体决定的,如果没有方法,一个class将只有结构。

    原型链的尽头为null,要明确一个对象的原型链到底包括什么,这里可以大概分为以下

    • 如果是个new Object()或字面量{},其原型链为
    var obj={}
    //原型链 obj=>Object.prototype=>null
    
    • 如果是new调用了其他构造函数,包括自定义的或者内置的(比如Array,内置构造函数不一定需要new调用,比如也可以通过字面量或者不使用new,比如[]Date()),这里以Array为例
    var arr =[]
    //arr=>Array.prototype=>Object.prototype=>null
    //如果是自定义构造函数也一样
    var P=function(){}
    var p=new P()
    //p=>P.prototype=>Object.prototype=>null
    
    • 如果在上一种情况下,延长原型链,其实就是怎么样实现继承
    //1. 使用Object.create()
    var q=Object.create(p)
    // q=>p
    
    //2. 直接修改构造函数的prototype
    function Q(){}
    Q.prototype=p
    var q=new Q()
    //q=>p
    //3. 通过Object.setPrototypeOf(obj, prototype)设置__proto__属性,可以直接修改原型链,这个操作很浪费性能,少用
    function P(){
        this.b=1
    }
    function Q(){
        this.a=2
    }
    var q=new Q()
    Object.setPrototypeOf(q,P.prototype)
    // q=>P.prototype
    //4. 使用call和apply借用构造函数时,和原型无关
    var P=function(v){
        this.a=v
    }
    function Q(v){
        P.call(this,v)
    }
    var q=new Q(2)
    // q=>Q.prototype=>Object.prototype=>null
    

    我们可以通过object instanceof constructor 判断一个构造函数的prototype是否在指定对象的原型链中

    function Q(){
        this.a=2
    }
    var q=new Q()
    console.log(q instanceof Q)
    console.log(q instanceof Object)
    

    可以通过Object.getPrototypeOf(object)获得对象的__proto__属性

    Object

    Object.prototype上有一些属性和方法被其他所有对象继承,在特定对象继承过程中可能会对某些字段重写。
    另外Object上还有很多静态方法用于处理关于对象的各种操作,具体请参考mdn

    js中的函数

    在js中所有函数都是Function的实例,包括Object和Function本身,乃至各种内置构造函数(比如Array),因此有

    Function.__proto__===Function.prototype//true
    Function.prototype.__proto__===Object.prototype//true,即Function instanceof Object
    //原型链 Function=>Function.prototype=>Object.prototype,以下类似
    
    Object.__proto__===Function.prototype//true,即Object instanceof Function
    Array.__proto__===Function.prototype //true
    
    function a(){}
    a.__proto__===Function.prototype//true
    
    

    可见,所有函数的原型链到达Object.prototype之前需要先经过Function.prototype,一个函数是一个对象,更是一个函数。

    Function

    Funciton.prototype上有一些方法值得我们关注

    • func.apply(thisArg, [argsArray])
    • function.call(thisArg, arg1, arg2, ...)
    • function.bind(thisArg[, arg1[, arg2[, ...]]])

    其中前两个在一个对象的上下文应用另一个对象的方法,第三个用于修改上下文,其余参数会在返回的函数调用时使用

    js的函数式编程

    在具体的了解函数式编程之前,这里先了解一些概念,参考Composing Software。

    概念

    Pure Function

    一个纯函数是一个函数,符合以下特点

    • 相同的输入总是返回相同输出
    • 没有副作用

    纯函数在函数式编程中很重要,但是实际的开发中,函数或多或少会有一些副作用,比如数据获取和操作dom。

    Function Composition

    函数复合是将两个或多个函数按照顺序生成一个函数或者执行操作。

    Shared State

    共享数据可以是变量、对象或内存空间。使用共享数据的一个问题是为了了解一个函数的副作用,需要知道每个共享数据的操作历史,比如对一个用户信息在不同终端的修改会发生冲突,因此在flux中要使用单向流。
    另一个问题是对共享数据的操作顺序也会造成不同结果,比如四则运算。

    Immutability

    一个不可变对象是创建后就不能改变,但是js在语言层面只提供了原始类型的不可变性,对对象并不提供这种特性,即使使用Object.freeze()等方法也只能冻结某个层级的对象修改,要想使用不可变数据,可以使用第三方库,比如Immer。
    不可变对象是函数式编程的核心概念,没有不可变性,程序中的数据流就会不可控,应该使用原数据生成新数据,而不应该修改原来的数据。

    在实际的操作中,对于一个特定的数据,不可变性和不同享,至少要满足一个。

    Side Effects

    副作用指的是除了对输出结果操作以外其他的操作,比如打印日志或修改dom,副作用在函数式编程中应该避免,即将副作用和数据流处理分开。

    Reusability Through Higher Order Functions

    高阶函数是任何以函数作为参数或返回函数的函数,经常用于

    • 使用回调函数、promise或monads对动作、副作用或异步数据流进行抽象或隔离。
    • 为操作各种类型的变量创建工具函数
    • 为了复用或函数组合而创建偏函数或柯里化
    • 将一系列输入的函数串联返回一个函数组合

    Containers, Functors, Lists, and Streams

    这里包括上面提到的monads,可以参考Functors, Applicatives, And Monads In Pictures

    一个functor数据结构可以用于映射数据,比如 [1,2,3].map(x => x *2),换句话说,它是一个容器,会为内部的数据应用一个函数,当看到这个词时应该想到mappable
    在这里被映射的是一个数组,只要提供map api,其他数据结构应该也可以,一个按顺序处理的list可以看作是一个stream。

    Declarative vs Imperative

    函数式编程是一种声明式范式,声明式编程会将流控制过程抽象,而不是用一行行代码描述怎么做,对应的是命令式。 比如函数doubleMap命令式的写法

    const doubleMap = numbers => {
    const doubled = [];
    for (let i = 0; i < numbers.length; i++) {
    	doubled.push(numbers[i] * 2);
       }
     return doubled;
     };
     console.log(doubleMap([2, 3, 4])); // [4, 6, 8]
    

    声明式的写法

    const doubleMap = numbers => numbers.map(n => n * 2);
    
     console.log(doubleMap([2, 3, 4])); // [4, 6, 8]
    
    

    总结

    一个函数式编程应该有以下特征

    • 纯函数,而不是共享数据和副作用
    • 不可变,而不是可变数据
    • 函数组合,而不是命令式流控制
    • 泛型工具而不是对某些数据的特定方法
    • 声明式,而不是命令式
    • 表达式,而不是语句

    在js中的函数式应用可以参考A GENTLE INTRODUCTION TO FUNCTIONAL JAVASCRIPT系列,最终目的就是将应用中的整个逻辑切分到不同的函数中,然后将函数组合,完成最终的任务。
    在具体处理过程中注意函数式编程的各种特征。

    常见的函数组合方式包括

    • compose,又称为pipe
     const compose = (...functions) => flowIn => functions.reduceRight( ( acc,f ) => f(acc), flowIn )
    
    • curry,这里实现一个具体的柯里化
    const add = a => b => a + b;
    add(1)(2)
    

    js的面向对象编程

    js的以原型为基础的继承不太适合实现面向对象的封装、继承和多态,而es6在语言层面实现了class语法,可以很方便的采用其他语言实践总结而来的设计模式和设计原则,这里建议采用ts,具体可参考ts实现的23种设计模式和设计原则

    无论采用何种范式,最终都是要将各个模块组合成我们的软件。


    完结撒花


    起源地下载网 » js中的面向对象编程和函数式编程

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元