函数
函数的概念:
函数包含一组数据,它们是JavaScript的基础模块单元,用于代码复用、信息隐藏(封装)和组合调用(继承)。
函数用于指定对象的行为。
所谓编程,就是将一组需求分解成一组函数与数据结构的技能。
函数对象:
对象的角度(__proto__):JavaScript中的函数就是对象。对象是“键/值”对的集合并拥有一个连到原型对象的隐藏连接(这个隐藏连接就是__proto__)。对象字面量产生的对象连接到Object.prototype。函数对象连接到Function.prototype(该原型对象本身连接到Object.prototype)。每个函数在创建时会附加两个隐藏属性:函数的上下文(this:指向window)和实现函数行为的代码注释1。
函数的角度(prototype):每个函数对象在创建时也随机配有一个prototype属性,它的值是一个拥有constructor属性并且值为该函数的对象。这和隐藏连接到Function.prototype完全不同。
因为函数是对象,所以他们可以像任何其他的值一样被使用。函数可以保存在变量、对象和数组中。函数可以被当做参数传递给其他函数,函数也可以再返回函数。而且,因为函数是对象,所以函数拥有方法(静态方法和动态方法)。
function fn() { // 动态方法,需要new一个实例对象来调用 this.play = function () { console.log('玩耍') } } let f = new fn() f.play() // 玩耍 fn.play() // Uncaught TypeError: fn.play is not a function // 静态方法,不需要new一个实例,直接调用 fn.eat = function () { console.log('吃饭') } fn.eat(); // 吃饭
函数字面量:
函数对象可以通过函数字面量来创建:
let add = function fn(a, b) { return a + b }; console.log(add(10, 20)) console.log(add(101, 201))
函数字面量包括4个部分:
1、保留字function
2、函数名,就是fn,它可以被省略。函数可以用它的名字来递归地调用自己,这个名字可以被调试器和开发工具用来识别函数。如果没有给函数命名,就是匿名函数
3、函数的参数,多个参数用逗号分隔。这些参数被定义为函数中的变量。它们不像普通的变量那样被初始化为undefined,而是在函数被调用时初始化为实际提供的参数的值
4、函数体,函数被调用的时候执行
函数字面量可以出现在任何允许表达式出现的地方。函数也可以被定义在其他函数中。一个内部函数除了可以访问自己的参数和变量,同时它也能自由访问把它嵌套在其中的复函数的参数与变量(作用域链:内部函数可以访问到外部函数的变量,而外部函数不可以访问内部函数的变量)。通过函数字面量创建的函数对象包含一个连到外部上下文的连接,这就是闭包。
调用:
调用一个函数会暂停当前函数的执行,传递控制权和参数给新函数(JavaScript是单线程,当前函数执行完了再去执行下一个函数,就是入栈和出栈的概念)。除了声明时定义的形式参数,每个函数还接收两个附加的参数:this和arguments。参数this在面向对象编程中非常重要,它的值取决于调用的模式。在JavaScript中一共有4中调用模式:方法调用模式、函数调用模式、构造器调用模式、apply调用模式。这些模式在如何初始化关键参数this上存在差异。
调用运算符就是函数名后面的小括号。小括号里可以有零个或多个用逗号隔开的表达式(表达式:有可能是别的函数运行的结果放在这里当做参数使用,所以这里叫表达式),每个表达式产生一个参数值。每个参数值被赋予函数声明时定义的形式参数名。当实参的个数和形参的个数不一致时,不会导致运行错误。如果实参个数多于形参,超出的实参就相当于没传;如果实参比形参少,那剩下的形参默认为undefined。对参数值不会进行类型检查:任何类型的值都可以被传递给任何参数(JavaScript是弱类型的语言)。
方法调用模式:
当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this被绑定到该对象。如果调用表达式包含一个提取属性的动作(即包含一个.点表达式或[]下标表达式),那么它就是被当做一个方法来掉调用。
let obj = { value: 100, eat: function (val) { console.log(this.value += typeof val === 'number' ? val : 1) } } obj.eat() document.writeln(obj.value) obj.eat(3000) document.writeln(obj.value)
方法可以使用this访问自己所属的对象,所以它能够从对象中取值或对对象进行修改。this到对象的绑定发生在调用的时候(JavaScript的作用域在执行的时候确定)。这个“超级”延迟绑定使得函数可以对this高度复用。通过this可取得对它们所属对象的上下文的方法称为公共方法。
函数调用模式:
当一个函数不是一个对象的方法时,那么它就是被当做一个函数来调用:
function add(a, b) { return a + b } let sum = add(3, 4) console.log(sum)
以此模式调用函数时,this被绑定到全局对象。外部函数被调用的时候,this应该绑定到外部函数的this变量,但是内部函数的this在JavaScript语言设计的时候没有按照这样的思路来,所以不能共享该方法对对象的访问权(this指向)。
解决方案:给该函数定义一个that变量,赋值为this,内部函数可以访问到外部函数的变量,就可以访问到that,其实就是访问到this。
let obj = { value: 0, increment: function (val) { this.value += typeof val === 'number' ? val : 1 } } obj.increment() document.writeln(obj.value) // 1 obj.increment(2) document.writeln(obj.value) // 3 function add(a, b) { return a + b } obj.double = function () { let that = this let helper = function () { that.value = add(that.value , that.value) } helper() } obj.double() document.writeln(obj.value) // 6
定义在double方法中的helper函数就是私有方法,它只能被double方法调用。helper函数中的this指向window,它是无法通过this直接访问到obj对象中的value值的,但是double方法是obj对象调用的,所以该方法中的this指向obj对象,然后在该函数中定义that赋值为this,就可以在helper函数中通过that,拿到obj中的value值并进行操作,这是非常常用的方法。如果这里不想使用that变量,还可以用将helper函数定义为箭头函数,箭头函数的this正好指向外层函数的this,这里就正好指向obj了。还有call方法可以改变this指向,在调用helper函数的时候,调用call方法,即helper.call(this),这里传入的this就是赋给that的this,此时helper函数中的this指向为obj。
构造器调用模式:
JavaScript是一门基于原型继承的语言,这意味着对象可以直接从其他对象继承属性。构造器调用模式就是在一个函数(构造函数)前面使用new操作符来调用,这个操作在背地里会创建一个连接到该函数的prototype的新对象,同时this会被绑定到那个新对象上。
new操作符干了哪些事:
1、let obj{} 在内存中开辟一块新的空间,创建了一个对象
2、this=obj 将this的指向改为该对象
3、将构造函数上的属性和方法添加到新对象身上
4、return this 将该对象返回
function Fn(person) { this.name = '孙艺珍' this.name1 = person this.age = 20 this.love = function () { console.log('love') } } let f = new Fn('小明同学') console.log(f.name) // 孙艺珍 console.log(f.name1) // 小明同学 console.log(f.age) // 20 f.love() // love
apply调用模式:
改变this指向,如果第一个参数传入null则不改变this指向,如果apply和call只传入一个参数它俩没有任何区别。
let obj = { paly: function () { console.log(this) } } obj.paly() // {paly: ƒ} obj.paly.apply(this) // window obj.paly.call(window) // window obj.paly.call() // window obj.paly.call(null) // window
function fn() { console.log(this) } fn() // window let obj = { name: 'syz' } fn.apply(obj) // { name: 'syz' } fn.call(obj) // { name: 'syz' }
参数:
arguments是函数被调用时,传递给函数的参数列表,是一个伪数组。
伪数组:有length且大于0,但是没有数组的方法。
将伪数组转为数组:Array.from(伪数组) Array.prototype.slice.call(伪数组) Array.prototype.concat.apply([],伪数组)
function add(a, b) { console.log(arguments) // [3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ] console.log(arguments.length) // 2 console.log(typeof arguments) // object console.log(Object.prototype.toString.call(arguments)) // [object Arguments] } add(3, 4)
返回:
return可以使函数提前返回,并且可以返回一个值。
如果return后面没有内容,则返回值是undefined
如果函数调用时在前面加了new操作符,当返回值是一个对象时,返回该对象,当返回值不是一个对象时,返回this,就是new出来的实例对象----构造函数中一般不写return
function fn() { return 'aaa' } function ff() { this.age = 100 // return {} 如果这里返回一个对象,则ff()、new ff()、f的返回值都是该对象;如果返回值不是对象,则new ff()、f返回this(实例对象);如果没有返回值,则ff()返回undefined } let f = new ff() console.log(fn()) // aaa console.log(ff()) // undefined console.log(new ff()) // {age: 100} console.log(f) // {age: 100}
异常:
异常是干扰程序正常运行的事故。
// 用throw抛出一个异常对象 let add = function (a, b) { if (typeof a !== 'number' || typeof b !== 'number') { throw { name: 'TypeError', message: 'add needs numbers' } } return a + b } // console.log(add('a',3)) let try_it = function () { try { add(5, '6,') } catch (e) { document.writeln(e.name + ':' + e.message) console.log(e, e.name + ':' + e.message) } } try_it()
扩充类型的功能:
js允许给语言的基本类型扩充功能,比如给Object.prototype添加方法,所有的对象都可以使用。这样的方式对函数、数组、字符串、数字、正则、布尔值都适用。
Function.prototype.method = function (name, func) { if (!this.prototype[name]) this.prototype[name] = func return this } // 添加一个取整方法 Number.method('integer', function () { return Math[this > 0 ? 'ceil' : 'floor'](this) }) console.log((10.1).integer()) // 11 向上取整 // 添加一个去除字符串两侧空格方法 String.method('trim1', function () { return this.replace(/^\s+|\s+$/g, '') }) console.log('"' + ' neat '.trim1() + '"') // "neat"
注意:这个方法最好是中午用,因为早晚会出事的。知道js有这个功能就行了
递归:
直接或间接地调用自己的一种函数。
注释1:JavaScript创建一个函数对象时,会给该对象设置一个“调用”属性。当JavaScript调用一个函数时,可理解为调用此函数的“调用”属性。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· 地球OL攻略 —— 某应届生求职总结