Skip to content
On this page

函数

关于函数的一切,函数定义,属性,常用的函数编程...

作用域

作用域 的意义是可以让变量访问到自己可以访问的(小范围优先原则),保护其他变量不受污染,所以局部变量的作用域仅限于它所在的函数,一旦函数走完,作用域结束,生命周期就结束了

动态作用域的意义是为了更加灵活的使用变量, 也是为了代码复用, 通过改变 this 指向,可以实现代码的复用

块级作用域

let 创建块级作用域的意思是以 {}包裹可以形成一个代码块,比如在 if/for 中, let 在 该代码块中有效

js
if(true){
	let x = 10
}
// x is not defined
console.log(x) 

静态作用域

在函数定义的时候,带了一个[[scope]]的属性,包含着创建时的词法环境; 在函数创建已经确定好了函数的静态作用域了
比如 你是一个河南人,在出生的时候已经确定了,有些东西不论带到哪里都去不掉

js
// 普通函数
function ordinary() {
	return '我是普通的函数'
}
console.dir(ordinary)
// 闭包
function closure_Fn() {
	var num = 1
	return function () {
		return ++num
	}
}

var add = closure_Fn();
console.dir(add);

⭐动态作用域

TIP

特指 this,可以根据调用方的不同,切换不同的执行环境,也就是上下文,也是 调用者的地址值

箭头函数在创建时候就已经确定this指向,《开墙透绿》

回调函数中的 this 指向 window

js
var name = 1;

var obj1 = {
	name:2,
	fn1:function(){
		console.log(this.name);
	},

	fn2:()=>console.log(this.name),

	fn3:function(){
		return function(){
			console.log(this.name)
		}
	},

	fn4:function(){
		// 和 fn4 的 this 保持一致
		return ()=>{
			console.log(this.name)
		}
	},
}

let obj2 ={
	name:3
}

// 函数 在 堆内存里存储
// fn1 -> 指向一个地址, 这个地址 指向 这个函数地址
// 所以可以在其他调用环境中执行
// 其实this 指向的 执行环境

obj1.fn1() // 2
obj1.fn1.call(obj2) // 3
obj1.fn2() // 1
obj1.fn2.call(obj2) // 1
obj1.fn3()() // 1
obj1.fn3.call(obj2)() // 1

obj1.fn4()() // 2
obj1.fn4.call(obj2)() // 3

函数与方法的区别

js
// 函数
// function
function a(){

}


// 方法
let x = {
	// method
  init(){
    
  }
}

可以悬浮上去看到他们的直接的不同,方法要与指定的对象绑定

⭐闭包

TIP

函数的定义和执行不在同一个作用域内,叫做闭包 以前的定义是 外层函数引用内
部变量,导致内部变量无法释放

闭包是带数据的行为,对象是带行为的数据

js
function after(times,callback){
 return function(){
   if(--times){
    callback()
  }
 }
}

let cb = atfter(2,function(){
  console.log(11)
})

函数科里化

TIP

科里化(Currying)是一种将接受多个参数的函数转换成一系列只接受 单个参数 的函数的技术  
如果是 多个参数,叫 偏函数, 先固定部分参数,之后再传入其他参数  

sum( 1, 2 )( 3 )这种叫偏函数  
sum( 1 )( 2 )( 3 ) 这种叫科里化 

其实是利用了 闭包 效果对参数进行了缓存

js
function add( a, b, c ){
	return a + b + c
}

function curry(fn){
	// 获取形参的长度
	let length = fn.length;
	return function c(...args){
		if(args.length < length){
			return (...rest)=> c(...rest,...args)
		}
		return  fn(...args)
	}
}

let c = curry(add)
console.log("🚀 ~ file: index.js ~ line 78 ~ c(1,2,3)", c(1,2,3));
console.log("🚀 ~ file: index.js ~ line 79 ~ c(1)(2,3)", c(1)(2,3));
console.log("🚀 ~ file: index.js ~ line 80 ~ c()(1,2,3)", c()(1,2,3));

管道

fns 如果只有一项的话,是会原路返回,不会执行 reduce 方法,

从左到右是 管道,从右到左是 compose
js
function add1(a) {
  return a + 1
}
function add2(a) {
  return a + 2
}
function add3(a) {
  return a + 3
}


function pipe(...fns){
  // 从右到左执行
  // 1. a 是 add1,b 是 add2, 返回一个 (...args)=> add1(add2(...args))
  // 2. a 是 (...args)=> add1(add2(...args)), b 是 add3, (...args)=> add1(add2(add3(args)))

  let res = fns.reduceRight((a,b)=>{
    return (...args) => a(b(...args))
  });
  return res
}
let fn =  pipe(add1,add2,add3)
let res = fn("abcd")
let fn2 =  pipe(add1)
let res2 = fn2(10) // 11

pointFree(无指向)

我们在处理数据过程,定义成一种与参数无关的合成运算简单来说: 只使用函数运算,不关心值的传递

不显示的指出所带参数 例子:

js
var addOne = x => x + 1;
var square = x => x * x;

// 合并成一个运算

var addOneThenSquare = R.pipe(addOne, square);
addOneThenSquare(2) //  9

上层运算不会直接操作数据,而是通过底层函数去处理,这就要求将一些常用的操作封装成函数

函子

它是一个容器,包含了值和值的变形关系(变形关系指的是函数)。

Pointed 函子

实现了 静态方法 of,它避免了使用 new 来创建对象,更深层次的含义是 of 方法用来 把值放到上下文 context 中 (即:把一个值放到容器中,可以使用 map 方法来处理值 )

js
class Pointed {
  // 静态方法,避免使用 new 来创建对象
  static of (value) {
    return new Pointed(value)
  }

  constructor (value) {
    this._value = value
  }

  map (fn) {
    return Pointed.of(fn(this._value))
  }
}

let pointed = Pointed.of(3).map(val => val + 1).map(val => val * 2)

// 输出:Pointed { _value: 8 }
console.log(pointed)

Maybe 函子

js
class MayBe {
  constructor(value){
    this.value  = value
  }
 static of(value){
    return new MayBe(value)
  }
  map(fn){
   return this.value ?  new MayBe(fn(this.value)) : this
  }
}
let r =  MayBe.of(10).map(x=>x+2 )
console.log(r.value)

惰性函数

js
let reg = false;
function getCss(a, b) {
  console.log(reg)
	if (reg) {
		getCss = function (a, b) {
			return a + b;
		};
	} else {
		getCss = function (a, b) {
			return a - b;
		};
	}
	// 别忘了 return
  return getCss(a,b)
}
console.log(getCss(1,2))
console.log(getCss(1,3))

链式调用

js
function handleClass(el) {
      let methods = {
        addClass,
        removeClass,
      };

      function addClass(c) {
        el.classList.add(c);
        return methods;
      }

      function removeClass(c) {
        el.classList.remove(c);
        return methods;
      }

      return methods;
    }

 handleClass(prevBtn).addClass("disabled").removeClass("active");

AOP 编程

切面编程,相当于在面包片之间抹上一层黄油,不影响核心代码的实现

js
let say = function (val) {
  console.log(val);
};

Function.prototype.before = function (callback) {
  return () => {
    callback();
    console.log(this);
  };
};

let newSay = say.before(() => {
  console.log("before say");
});

newSay();

尾递归

尾递归的要求是递归 return 是一个函数, return fn() + 1 不能算是一个尾递归

js
function factorial(n, total = 1) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5) // 120
js
// 这道题有个规律,第一项加上第二项永远等于第三项:
// 1 + 1 = 2;1 + 2 = 3;2 + 3  // = 5;3 + 5 = 8 ....

// 倒序相加
function factorial(n) {
  // 第一项和第二项都返回1
  if (n === 1 || n === 2) return 1;
  // 我们只要返回 n - 1(n的前一项)与 n - 2(n的前两项)的和便是我们要的值
  return factorial(n - 1) + factorial(n - 2);
}


function factorial(n, sum1 = 1, sum2 = 1) { // 求最终的值,不是求和
  if (n === 1 || n === 2) return sum2;
  return factorial(n - 1, sum2, sum1 + sum2);
}

传统递归

尾递归优化

函数实现

链表

js
/*
How it works:
`this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value.
*/

class Node {
	value;
	next;

	constructor(value) {
		this.value = value;
	}
}

export default class Queue {
	#head;
	#tail;
	#size;

	constructor() {
		this.clear();
	}

	enqueue(value) {
		const node = new Node(value);

		if (this.#head) {
			this.#tail.next = node;
			this.#tail = node;
		} else {
			this.#head = node;
			this.#tail = node;
		}

		this.#size++;
	}

	dequeue() {
		const current = this.#head;

		if (!current) {
			return;
		}

		this.#head = this.#head.next;
		this.#size--;
		return current.value;
	}

	clear() {
		this.#head = undefined;
		this.#tail = undefined;
		this.#size = 0;
	}

	get size() {
		return this.#size;
	}

	* [Symbol.iterator]() {
		let current = this.#head;

		while (current) {
			yield current.value;
			current = current.next;
		}
	}
}

迭代器函数

yield 出产

执行 generator 时,会返回一个迭代器
迭代器是一个对象,包含 next 方法,调用 next 方法时,会返回一个对象
包含 valuedone 属性,value 表示当前迭代到的值,done 表示是否迭代完成

遇到 yield,就会暂停执行,直到生成器的 next 方法再次调用,然后返回生成器的新值,下一次调用 next() 时,在 yield 之后紧接着的语句继续执行。

如果将参数传递给生成器的 next() 方法,则该值将成为生成器当前 yield 操作返回的值。

TIP

yield 关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器的版本的 return 关键字。

js
let generator = function* (n) {
  console.log(n) // 10
  yield 1;
  yield 2;
};

let iterator = generator(10);
let a = iterator.next(); // { value: 1, done: false}
let b = iterator.next();  // { value: 2, done: false}

面试题

js
function* test(x) {
	const y = 2 * (yield (x + 1))
	const z = yield (y / 3)
	// x = 5, y = 24,z = 13
	console.log('x', x, 'y', y, 'z', z)
	return x + y + z
}

const b = test(5) // 返回一个生成器函数
console.log(b.next()) // 第一次 next 传递参数没用,因为是传递给 yield 返回值的,所以 x = 6
console.log(b.next(12)) // 传递参数后,yield 返回的值就是 12,所以 y = 24,所以返回 8
console.log(b.next(13)) // z 变为了 13,所以就是  5 + 13 + 24 = 42

原型链

通过改变 init 的原型链,可以把 this 指向回到 JQuery

js
function JQuery(){
  return new JQuery.prototype.init();
}

JQuery.prototype.init = function(){
  console.log("init")
}

JQuery.prototype.css = function(){
  console.log("css")
}

JQuery.prototype.init.prototype = JQuery.prototype;

JQuery().css() // init css

否则

js
function JQuery(){}

JQuery.prototype.init = function(){
  console.log("init")
}

JQuery.prototype.css = function(){
  console.log("css")
}

let a = new JQuery;

a.init()
a.css()

constructor

当创建函数的时候,就同时创建了 constructor 方法指向了自己的构造函数

js
function A() {}
let a = new A;
console.log(A.prototype.constructor) // A:{}
console.log(a.constructor) // A:{}
console.log(A.constructor) // Function

由于是在 prototype 对象上,所以可以修改

js
A.prototype = {
	name:"zs"
}

为了防止修改 constructor 方法,需要再次修复

js
A.prototype.constructor = A

参数

js
function a(obj){
  obj.name ="zs"
}

let obj = {
  name:"lisi"
}

a(obj)
// {name:'zs'}
obj

可以看到 obj 被修改了,说明函数的参数传递是一个引用传递

函数重写