JavaScript入门9 📅 发布时间:2026/8/31 18:36:27 👁 浏览次数: 文章目录二十三、函数式编程1. 体验函数式编程1.1 JS 中的函数是一等公民1.2 函数的作用域链1.3 纯函数 vs 非纯函数2. 数组的常用函数式方法2.1 map - 数据转换2.2 filter - 数据筛选2.3 reduce - 数据聚合3. 闭包了解3.1 闭包的形成3.2 闭包的经典应用3.2.1 数据私有化模块模式3.2.2 函数工厂4. 函数式编程之高阶函数5. 函数式编程之函数组合6. 函数式编程之柯里化7. 实战练习7.1 练习1实现一个简单的计数器工厂7.2 练习2使用函数式方法处理数据二十四、原型对象1. prototype 是什么2. prototype 的作用2.1 实现继承2.2 共享属性和方法2.3 动态扩展功能3. 原型链的终点二十三、函数式编程函数式编程Functional Programming, FP是一种以函数为核心的编程范式函数可以作为参数返回值变量等。1. 体验函数式编程1.1 JS 中的函数是一等公民函数中可以嵌套函数函数可以作为方法的参数函数可以作为返回值。请看一下代码函数中嵌套函数函数作为返回值// 外部函数functionouter(){// 内部函数functioninner(username){console.log(Hello${username});}// 函数也是对象可以将函数返回给调用者。returninner;}// 调用者无法直接访问内部函数//inner(); // 报错inner is not defined// 可以通过调用外部函数拿到内部函数innerFun是一个指向内部函数的一个变量constinnerFunouter();// 虽然是变量但可以当做内部函数使用。innerFun(Tom);函数也可以作为参数如下代码functionfun(funParam){funParam(Tom);}fun(functionsayHello(username){console.log(hello${username});});// 当然以上函数也可以以箭头函数的方式传递fun((username){console.log(Hi,${username});});1.2 函数的作用域链在JavaScript中作用域链的工作方式是内层函数可以访问外层函数的变量但外层函数不能访问内层函数的变量。functiona(){letaVara函数中的变量;functionb(){console.log(在b函数中可以访问a函数中的变量${aVar});letbVarb函数中的变量;console.log(在b函数中当然可以访问b函数中的变量${bVar});}// 调用函数bb();//console.log(在a函数中无法访问b函数中的变量这样会报错${bVar});}// 调用函数aa();1.3 纯函数 vs 非纯函数纯函数以下是纯函数的特点只要传入的参数输入相同函数就一定会返回相同的结果输出不会有任何意外情况。值是稳定的。只依赖输入参数不依赖外部变量全局变量、外层作用域变量等。不会修改外部状态不改变全局变量、不修改传入的参数、不执行 I/O 操作等。纯函数 vs 非纯函数// 纯函数functionsquare(x){returnx*x;}// 非纯函数lettaxRate0.1;functioncalculateTax(amount){returnamount*taxRate;// 依赖外部状态}2. 数组的常用函数式方法2.1 map - 数据转换map 方法遍历数组的每个元素按照你提供的规则转换它们并返回一个新数组。map 的参数是一个函数通过这个函数来描述转换规则constnumbers[1,2,3];constsquaresnumbers.map(xx*x);// [1, 4, 9]2.2 filter - 数据筛选filter 的参数是一个函数通过这个函数来描述过滤规则constnumbers[1,2,3,4,5];constevensnumbers.filter(xx%20);// [2, 4]2.3 reduce - 数据聚合constnumbers[1,2,3,4];constsumnumbers.reduce((acc,x)accx,0);// 103. 闭包了解3.1 闭包的形成通俗理解闭包就像是一个背包当一个函数出生时它会随身携带一个背包里面装着它出生时周围的所有变量。核心特点函数可以记住并访问它被创建时的作用域即使这个函数在其他地方执行生活比喻就像你搬家到另一个城市但仍然记得老家的方言和习惯。代码示例functioncreateGreeter(greeting){// 内部函数使用了外部函数的变量这样的内部函数可以看做是一个闭包returnfunction(name){console.log(${greeting},${name}!);};}// 内部函数出生sayHello、sayHi就是出生的新函数的名字。constsayHellocreateGreeter(Hello);// createGreeter函数在上面已经执行结束了// 但调用内部函数的时候内部函数还可以继续使用当时出生时的外部函数的变量。sayHello(Alice);// Hello, Alice!在这段代码中闭包的体现是从 return function(name) {…} 开始直到这个内部函数结束即 } 之前。具体来说闭包体现在内部函数 function(name) {…} 访问了外部函数 createGreeter 的参数 greeting即使外部函数 createGreeter 执行完毕内部函数仍然保持着对 greeting 的引用完整定义闭包的语法returnfunction(name){console.log(${greeting},${name}!);};闭包的关键特征是● 函数嵌套闭包被嵌套在一个函数中或一个对象中● 内部函数引用了外部函数的变量● 内部函数被返回可以在外部函数执行完毕后继续使用所以当你这样使用时constsayHellocreateGreeter(Hello);sayHello(Alice);// 仍然能访问到 Hello 这个 greetingsayHello 函数就是一个闭包它记住了创建时的 greeting 值。闭包 函数 该函数访问的外部变量3.2 闭包的经典应用3.2.1 数据私有化模块模式问题如何创建真正的私有变量解决方案使用闭包functionperson(){letusernamejack;return{// 闭包getUsername(){returnusername;},// 闭包setUsername(newName){usernamenewName;}};}constpperson();// 这个代码看起来p对象中是有username属性的console.log(p.getUsername());p.setUsername(lucy);console.log(p.getUsername());// 像私有属性无法访问console.log(p.username);p对象没有真正的属性username而是通过闭包实现看着是私有属性的样子3.2.2 函数工厂应用场景创建多个功能类似但配置不同的函数代码示例functioncreateMultiplier(factor){// 闭包returnfunction(number){returnnumber*factor;};}// 创建一个完成两倍的函数。// 内部函数double出生时记住了外部函数的factor2constdoublecreateMultiplier(2);// 创建一个完成三倍的函数。consttriplecreateMultiplier(3);console.log(double(5));// 10console.log(triple(5));// 154. 函数式编程之高阶函数定义什么叫做高阶函数如果一个函数的参数是函数或者一个函数的返回值是一个函数我们把这个函数称为高阶函数。代码示例// 该函数的第一个参数是一个函数因此 doTwice 被称为高阶函数。functiondoTwice(func,value){returnfunc(func(value));}functionadd5(x){returnx5;}console.log(doTwice(add5,10));// 20 (10515, 15520)5. 函数式编程之函数组合概念函数组合就是把多个函数合并成一个新函数这个新函数会从右到左或从左到右依次执行这些函数。// compose函数实现了函数的组合。functioncompose(...fns){// 闭包functioncompute(x){// reduce()方法是从左到右// reduceRight()方法是从右到左// 遍历数组fns然后取出数组中每一个函数f(reduceRight是从数组的右侧开始取函数)// 第一轮x是初始值初始值会自动赋值给累加器的第一个参数。这是累加器的固定规则。returnfns.reduceRight((v,f)f(v),x);}returncompute;}constadd1xx1;constdoublexx*2;constsquarexx*x;// transform是计算函数compute// 含义先求和将求和的结果扩大2倍将扩大2倍后的数据再求平方。从右向左函数组合。consttransformcompose(square,double,add1);console.log(transform(2));// ((2 1) * 2) * ((2 1) * 2) 366. 函数式编程之柯里化定义将多参数函数转换为一系列单参数函数的技术英文叫 Currying名字来源于数学家 Haskell Curry// 普通函数functionadd(a,b,c){returnabc;}// 柯里化版本functioncurryAdd(a){// 闭包returnfunction(b){// 闭包returnfunction(c){returnabc;};};}// 使用箭头函数简化constcurryAddabcabc;console.log(curryAdd(1)(2)(3));// 6// 实用示例创建特定功能的函数constadd5curryAdd(2)(3);// 固定前两个参数sconsole.log(add5(10));// 15 (2310)柯里化最大的作用就是可以固定部分参数的值。7. 实战练习7.1 练习1实现一个简单的计数器工厂functioncreateCounter(){// 在这里实现letcount1;return()count;}constcounter1createCounter();console.log(counter1());// 1console.log(counter1());// 2constcounter2createCounter();console.log(counter2());// 17.2 练习2使用函数式方法处理数据constusers[{name:Alice,age:25},{name:Bob,age:30},{name:Charlie,age:20}];// 1. 获取所有用户的名字提示用数组的map方法constnamesusers.map(useruser.name)// 2. 获取年龄大于25的用户提示用数组的filter方法constuserusers.filter(useruser.age25);// 3. 计算所有用户的平均年龄提示用数组的reduce方法constavgAge(users.map(useruser.age).reduce((a,b)ab))/users.length;二十四、原型对象在 JavaScript 中对象的 prototype 属性是理解原型继承Prototypal Inheritance的核心概念。以下是详细解释1. prototype 是什么● 函数的 prototype 属性只有函数对象包括构造函数拥有显式的 prototype 属性。返回值被称为原型对象用于存储该函数作为构造函数时所有实例共享的属性和方法。functionPerson(name){this.namename;}// 函数的 prototype 属性Person.prototype.sayHellofunction(){console.log(Hello,${this.name}!);};● 对象的 [[Prototype]] 链普通对象非函数没有 prototype 属性但有一个内部指针 [[Prototype]]可通过proto或 Object.getPrototypeOf() 访问。它指向该对象的原型对象即其构造函数的 prototype。constjohnnewPerson(John);console.log(john.__proto__Person.prototype);// trueconsole.log(Person.prototypeObject.getPrototypeOf(john));// true原型对象的作用原型对象用于共享属性和方法。所有通过该构造函数创建的实例都会继承原型对象上的成员。2. prototype 的作用2.1 实现继承当访问一个对象的属性/方法时如果对象自身没有该成员JavaScript 会通过proto向上查找其原型对象若仍未找到则继续查找原型对象的原型对象直到顶层Object.prototype这种链式结构称为原型链。示例console.log(john.toString());// 调用的是 Object.prototype.toString()查找过程检查 john 自身是否有 toString() → 无检查 john.proto即 Person.prototype→ 无检查 Person.prototype.proto即 Object.prototype→ 找到调用JS 中默认情况下继承是几层● 自定义构造函数创建的对象三层实例 → 构造函数.prototype → Object.prototype → null● 字面量对象 {}两层实例 → Object.prototype → null实现继承关系// 父类functionShape(color){this.colorcolor;}Shape.prototype.getColorfunction(){returnthis.color;};// 子类functionCircle(color,radius){// 调用父类构造方法Shape.call(this,color);// 子类特有属性this.radiusradius;}// 继承原型// 这是必须的如果不写这一行就不算完整的原型继承。// 如果不写Circle的实例将无法继承Shape原型上的方法如getColor。Circle.prototype会直接指向默认的Object.prototype而不是Shape.prototype。原型链就断了。// Object.create(Shape.prototype); 代码作用创建一个新对象这个新对象的原型对象父对象指向Shape.prototypeCircle.prototypeObject.create(Shape.prototype);// 这一行不是绝对必须的但强烈建议写上如果不写Circle.prototype.constructor会指向Shape。Circle.prototype.constructorCircle;Circle.prototype.getAreafunction(){returnMath.PI*this.radius*this.radius;};constcirclenewCircle(red,5);console.log(circle.getColor());// 输出: redconsole.log(circle.getArea());// 输出: 78.539816339744832.2 共享属性和方法原型链上的所有属性和方法都是所有实例共享的目的是节省内存constalicenewPerson(Alice);console.log(john.sayHelloalice.sayHello);// true2.3 动态扩展功能即使实例已创建修改原型仍会影响所有实例Person.prototype.goodbyefunction(){console.log(Goodbye,${this.name}!);};john.goodbye();// 动态新增的方法对所有实例生效当然内置的对象也可以动态扩展功能Array.prototype.lastfunction(){returnthis[this.length-1];}constarr[1,2,3];console.log(arr.last());// 输出: 33. 原型链的终点原型链的顶端是 Object.prototype其 [[Prototype]] 为 nullconsole.log(Object.getPrototypeOf(Object.prototype));// null区分自身属性和原型属性Object 的 hasOwn 方法用来区分自身属性和原型属性functionCar(model){this.modelmodel;}Car.prototype.brandToyota;constmyCarnewCar(Camry);console.log(Object.hasOwn(myCar,model));// true自身console.log(Object.hasOwn(myCar,brand));// false原型