理解js函数(Ⅱ)
1、函数的尾调用优化是什么?
在 JavaScript 中,尾调用优化(Tail Call Optimization, TCO) 是一种优化技术,用于优化递归函数调用。当一个函数的最后一个操作是调用另一个函数(即尾调用)时,引擎可以重用当前的栈帧,而不是创建新的栈帧。这种优化可以防止因递归深度过大而导致的栈溢出(stack overflow)问题,并提高性能。
定义:
尾调用指的是一个函数在它的执行上下文中,最后一个执行的操作是调用另一个函数,并且调用返回的结果直接作为当前函数的返回值。例如:
function tailCallExample(x) {if (x === 0) return 1;return x * factorial(x - 1); // 这不是尾调用,因为结果需要计算 x * ...
}function factorial(x, acc = 1) { // 使用累加器参数实现尾调用if (x === 0) return acc;return factorial(x - 1, x * acc); // 这是尾调用
}
条件:
- 尾位置:函数调用必须是函数体的最后一个操作。
- 返回值直接传递:调用结果直接作为当前函数的返回值,没有其他计算。
- 无副作用:调用后没有需要保留的上下文(如局部变量等)。
2、什么是闭包?
定义:
闭包是指那些引用了另一函数作用域中变量的函数。举例:
function test() {let result;return function(a,b){result = a + b;return result;}}let sum=test();
调用test()方法,返回匿名函数。匿名函数中引用了外层函数test中的result这个变量。因此,在test函数执行完毕后,他的执行上下文和作用域链会被销毁,但是他的活动对象并不会被销毁。这样会造成内存泄漏,除非你手动进行销毁:
sum=null
闭包中this的指向
1)箭头函数的this指向定义箭头函数的上下文对象
2)普通函数中的this指向调用函数的对象。
3)匿名函数中的this通常指向window。(非严格模式下)
this和arguments都是不等你直接在内部函数中访问的。通常需要将其引用先保存到闭包能访问到的另一个变量中:
let name = 789;let obj = {name: "123",test() {let result;console.log(this.name)let that = this;return function (a, b) {result = a + b;console.log(that.name);console.log(typeof this.name);};},};obj.test()();
这里注意let声明的name并不会挂到全局上下文的变量对象中,因此你在闭包函数中的this.name访问全局的name是空字符串,而不是789;
let name = 789;let obj = {name: "123",test() {let result;console.log(this.name)let that = this;return (a, b)=> {result = a + b;console.log(that.name);console.log(this.name);};},};obj.test()();
箭头函数中的this指向obj,因为箭头函数定义在test函数的上下文中,他的this就是箭头函数的this,test的this指向obj。因此箭头函数中的this时钟指向obj。
3、立即执行函数表达式
立即执行函数参数产生的目的是实现块级作用域效果的目的,锁住变量的值(类似let)。
举个例子:
let divs=document.querySelectorAll('div')for(var i=0;i<divs.length;i++){divs[i].onclick=(function(j){return function(){console.log(j)}})(i)}
有了let实现:
let divs = document.querySelectorAll("div");for (let i = 0; i < divs.length; i++) {divs[i].onclick = function () {console.log(i);};}
4、私有变量
因为函数拥有自己的作用域和上下文,而且在这个函数或块的外部无法访问内部的变量,你可以将其理解为私有变量。
实现私有变量有几种方式:
A)构造函数
function Person(){//私有变量和方法let name = 'haha'function getName(){return name}//特权方法this.publicMethod=function(){console.log(name);getName();}}
通过在构造函数中定义私有变量和方法,只有通过Person的实例调用特权方法才能访问到。
以上这种方式存在构造函数的缺点,特权方法在每个实例中都是独立的。
B)改进
(function(){let name='haha'function getName(){return name}Person=function(){}Person.ptototype.publicMethod=function(){console.log(name);getName();}})()
把特权方法放在函数的原型上,解决构造函数特权方法不重用的问题。
注意上面的细节:Person没有用关键字声明,会被添加到全局作用域中,因此立即执行函数表达式执行完毕后,在全局作用域中能都使用Person创建实例。
C)模块模式
let singleton=function(){let name='haha'function getName(){return name}return {publicMethod:function(){console.log(name);getName();}}}()
这种模式是在一个匿名函数内部返回一个对象并将他赋值给了外部变量singleton,因为将私有变量和方法定义在匿名函数中,而返回的对象中的方法或属性是能够访问私有变量和方法的。
D)模块增强模式
let singleton = (function () {let name = "haha";function getName() {return name;}let obj = new Person();obj.publicMethod = function () {console.log(name);getName();};return obj;})();
以上这种模式,相较于模块模式,匿名函数中返回的对象可以是指定的类型。