函数
关于函数的一切,函数定义,属性,常用的函数编程...
作用域
作用域
的意义是可以让变量访问到自己可以访问的(小范围优先原则
),保护其他变量不受污染,所以局部变量的作用域仅限于它所在的函数,一旦函数走完,作用域结束,生命周期就结束了
动态作用域的意义是为了更加灵活的使用变量, 也是为了代码复用, 通过改变 this
指向,可以实现代码的复用
块级作用域
let
创建块级作用域的意思是以 {}
包裹可以形成一个代码块,比如在 if/for
中, let 在 该代码块中有效
if(true){
let x = 10
}
// x is not defined
console.log(x)
静态作用域
在函数定义的时候,带了一个[[scope]]的属性,包含着创建时的词法环境; 在函数创建已经确定好了函数的静态作用域了
比如 你是一个河南人,在出生的时候已经确定了,有些东西不论带到哪里都去不掉
// 普通函数
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
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
函数与方法的区别
// 函数
// function
function a(){
}
// 方法
let x = {
// method
init(){
}
}
可以悬浮上去看到他们的直接的不同,方法要与指定的对象绑定
⭐闭包
TIP
函数的定义和执行不在同一个作用域内,叫做闭包 以前的定义是 外层函数引用内
部变量,导致内部变量无法释放
闭包是带数据的行为,对象是带行为的数据
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 ) 这种叫科里化
其实是利用了 闭包 效果对参数进行了缓存
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 方法,
从左到右是 管道,从右到左是 composefunction 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(无指向)
我们在处理数据过程,定义成一种与参数无关的合成运算简单来说: 只使用函数运算,不关心值的传递
不显示的指出所带参数 例子:
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 方法来处理值 )
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 函子
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)
惰性函数
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))
链式调用
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 编程
切面编程,相当于在面包片之间抹上一层黄油,不影响核心代码的实现
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
不能算是一个尾递归
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5) // 120
// 这道题有个规律,第一项加上第二项永远等于第三项:
// 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);
}
传统递归
尾递归优化
函数实现
链表
/*
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
方法时,会返回一个对象
包含 value
和 done
属性,value
表示当前迭代到的值,done
表示是否迭代完成
遇到 yield
,就会暂停执行,直到生成器的 next
方法再次调用,然后返回生成器的新值,下一次调用 next() 时,在 yield 之后紧接着的语句继续执行。
TIP
yield 关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器的版本的 return
关键字。
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}
面试题
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
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
否则
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
方法指向了自己的构造函数
function A() {}
let a = new A;
console.log(A.prototype.constructor) // A:{}
console.log(a.constructor) // A:{}
console.log(A.constructor) // Function
由于是在 prototype
对象上,所以可以修改
A.prototype = {
name:"zs"
}
为了防止修改 constructor
方法,需要再次修复
A.prototype.constructor = A
参数
function a(obj){
obj.name ="zs"
}
let obj = {
name:"lisi"
}
a(obj)
// {name:'zs'}
obj
可以看到 obj
被修改了,说明函数的参数传递是一个引用传递