js红书 【3 代理 函数】

katsu 发布于 2025-02-17 115 次阅读


9 代理和反射

什么是 proxy

proxy 创造一个被代理的对象,当对这个对象进行一些特定操作,比如访问或者赋值的时候,拦截那个操作,然后执行 handler 中定义的对应操作。

Proxy 允许你定义一个通用的逻辑来处理所有属性访问,而不需要写大量冗余的代码。

const target = { message: "Hello" };
const handler = {
  get(obj, prop) {
    console.log(`读取属性 ${prop}`);
    return obj[prop];
  },
  set(obj, prop, value) {
    console.log(`设置属性 ${prop} 为 ${value}`);
    obj[prop] = value;
    return true;
  }
};

const proxy = new Proxy(target, handler);
console.log(proxy.message);  // 输出:读取属性 message / Hello
proxy.message = "Hi";          // 输出:设置属性 message 为 Hi

Reflect get set has deleteProperty apply construct ownKeys

const target = { a: 1 };
const handler = {
  get(obj, prop, receiver) {
    console.log(`拦截读取属性:${prop}`);
    // 使用 Reflect.get() 返回目标对象的默认属性值
    return Reflect.get(obj, prop, receiver);
  },
  set(obj, prop, value, receiver) {
    console.log(`拦截设置属性:${prop} 为 ${value}`);
    // 调用默认的赋值行为
    return Reflect.set(obj, prop, value, receiver);
  }
};

const proxy = new Proxy(target, handler);
console.log(proxy.a);  // 输出:拦截读取属性:a  然后输出 1
proxy.a = 42;          // 输出:拦截设置属性:a 为 42

10 函数

函数返回对象

简单易懂

函数也是对象

函数也是对象 所以可以挂载属性和方法 尤其是里面挂载函数大概 3 中

  1. 静态直接挂
    
    function FunctionClass() {
    // 构造函数逻辑
    }

// 添加一个静态方法
FunctionClass.functionExample = function() {
console.log("调用静态方法 functionExample");
};

FunctionClass.functionExample(); // 输出: 调用静态方法 functionExample

1. 模块化
2. ES6 的 class

## 疑问:js 里如何获取函数的变量 argu?

*arguments*对象 callee 属性保存原函数指针

**分析:**  
在非严格模式下,arguments 对象是**与函数参数绑定的**,所以修改 arguments[0] 也会影响 a 的值。但是,在 **严格模式 ("use strict") 下,arguments 不再与参数列表建立引用关系**,它们是分开的。

所以:

- arguments[0] = 10; **不会影响 aa 仍然是 1**
- arguments[1] = 20; **不会影响 bb 仍然是 2**

在 ES6 严格模式中(或任何 ES6 环境中),最推荐的方式是使用“剩余参数”(rest parameters)语法,这样你可以直接将所有传入的参数收集到一个数组中。例如:

```js
function myFunction(...args) {
  console.log(args); // args 已经是一个数组,包含所有传入的参数
}

myFunction(1, 2, 3); // 输出 [1, 2, 3]

或者你也可以用apply和call

this

 window.color = 'red';
    let o = {

      color: 'blue'
    };

    function sayColor() {
      console.log(this.color);

    }
    sayColor();    // 'red'

    o.sayColor = sayColor;
    o.sayColor();  // 'blue'

课本内容

函数实际上是对象。每个函数都是 Function 类型的实例,而 Function 也有属性和方法,跟其他引用类型一样。因为函数是对象,所以函数名就是 指向函数对象的指针,而且不一定与函数本身紧密绑定。

ECMAScript 函数既不关心传入的参数个数,

也不关心这些参数的数据类型。定义函数时要接收两个参数,并不意味着调用时就传两个参数。你可以传一 个、三个,甚至一个也不传,解释器都不会报错。

// 可以  
function ignoreFirst(firstValue, ...values) {

      console.log(values);
    }

ignoreFirst();
    ignoreFirst(1);
    ignoreFirst(1,2);
    ignoreFirst(1,2,3);  // [2, 3]

JavaScript 引擎在任何代码执行之前,会读取函数声明,并在执行上下文中 生成函数定义。而函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。

因为函数名在 ECMAScript 中就是变量,所以函数可以用在任何可以使用变量的地方。

传递函数 把函数作为值

  function add10(num) {
      return num + 10;

}

    let result1 = callSomeFunction(add10, 10);
    console.log(result1);  // 20

    function getGreeting(name) {
      return "Hello, " + name;

}

    let result2 = callSomeFunction(getGreeting, "Nicholas");
    console.log(result2);  // "Hello, Nicholas"

函数内部

this

 window.color = 'red';
    let o = {

      color: 'blue'
    };

    function sayColor() {
      console.log(this.color);

    }
    sayColor();    // 'red'

    o.sayColor = sayColor;
    o.sayColor();  // 'blue'

这个 this 到底引用哪个对象必须到 函数被调用时才能确定。

箭头函数则不是

箭头函数中的 this 会保留定义该函数时的上下文

 window.color = 'red';
    let o = {

      color: 'blue'
    };

    let sayColor = () => console.log(this.color);
    sayColor();    // 'red'

    o.sayColor = sayColor;
    o.sayColor();  // 'red'

上下文不同

function King() {  
this.royaltyName = 'Henry';  
// this 引用 King 的实例  
setTimeout(() => console.log(this.royaltyName), 1000);

}
function Queen() {
  this.royaltyName = 'Elizabeth';

// this 引用 window 对象
setTimeout(function() { console.log(this.royaltyName); }, 1000); }

new King();  // Henry
    new Queen(); // undefined

普通函数(如 function() { ... }):
普通函数会在调用时确定 this 的指向。如果没有明确绑定(例如使用 .bind() 或其他绑定方式),则默认情况下(在非严格模式下)this 会指向全局对象(浏览器中为 window;在严格模式下为 undefined)。

caller

这个属性引用的是调用当前函数的函数

new. target
检测函数是否使用 new 关键字调用的 new.target 属性

函数的属性和方法

前面提到过,ECMAScript 中的函数是对象,因此有属性和方法。每个函数都有两个属性:length 和 prototype。其中,length 属性保存函数定义的命名参数的个数

prototype 属性也许是 ECMAScript 核心中最有趣的部分。prototype 是保存引用类型所有实例 方法的地方,这意味着 toString()、valueOf()等方法实际上都保存在 prototype 上,进而由所有实 例共享。

函数还有两个方法:apply()和 call()。这两个方法都会以指定的 this 值来调用函数,即会设 置调用函数时函数体内 this 对象的值。

函数一共就那点玩意,定义操作和返回;传参和调用 这里看着像传参

function sum(num1, num2) {
      return num1 + num2;

}

function callSum1(num1, num2) {  
return sum.apply(this, arguments); // 传入 arguments 对象

}

function callSum2(num1, num2) {  
return sum.apply(this, [num1, num2]); // 传入数组

}

console.log(callSum1(10, 10));  // 20
console.log(callSum2(10, 10));  // 20
注意 在严格模式下,调用函数时如果没有指定上下文对象,则 this 值不会指向 window。 除非使用 apply()或 call()把函数指定给一个对象,否则 this 的值会变成 undefined。
"use strict";
function getMax(a, b, c) {
  console.log(this); // undefined
  return Math.max(a, b, c);
}

getMax.apply(this, [3, 7, 2]); // 可能会出问题

使用 call()或 apply()的好处是可以将任意对象设置为任意函数的作用域,这样对象可以不用关 心方法。

ECMAScript 5 出于同样的目的定义了一个新方法:bind()

对象不必“关心”方法:
这句话的“关心”可以理解为“需要拥有或管理”。由于你可以通过 call()apply() 来指定函数的执行上下文,你的对象不必包含该函数作为自己的属性,也不必重写这个方法就能利用其功能。换句话说,对象可以“借用”别处定义好的方法,而无需自己“关心”方法的具体实现。

函数表达式

let functionName = function(arg0, arg1, arg2) { // 函数体

};
// 没问题  
let sayHi;  
if (condition) {

      sayHi = function() {
        console.log("Hi!");
};

} else { 

  sayHi = function() {
    console.log("Yo!");
}; 
}

任何时候, 只要函数被当作值来使用,它就是一个函数表达式。

函数典中典之递归

arguments. callee 登场!

不过,在严格模式下运行的代码是不能访问 arguments.callee 的,因为访问会出错。

 function factorial(num) {
      if (num <= 1) {

        return 1;
      } else {

        return num * factorial(num - 1);
      }

}

这是经典的递归阶乘函数。虽然这样写是可以的,但如果把这个函数赋值给其他变量,就会出问题:

let anotherFactorial = factorial; factorial = null; console.log(anotherFactorial(4)); // 报错
arguments.callee 就是一个指向正在执行的函数的指针,因此可以在函数内部递归调用,如下 所示:

    function factorial(num) {
      if (num <= 1) {

        return 1;
      } else {

        return num * arguments.callee(num - 1);

} }

命名函数表达式(named function expression)达到目的

    const factorial = (function f(num) {
      if (num <= 1) {

        return 1;
      } else {

        return num * f(num - 1);

} });

尾调用优化

只有 Safari 支持尾递归优化(TCO),Chrome/Firefox/Node.js 都不支持!

  • V8(Chrome、Node.js)没有实现 TCO,所以 尾递归不会优化堆栈,还是会爆栈。
  • Safari(JavaScriptCore 引擎)支持 TCO,可以优化递归。

所以在 JavaScript 里,尾递归几乎没什么作用,建议用 循环 替代递归!🚀

闭包 之前学过了 还挺重要的

  • 当你在一个函数内部定义另一个函数,并且这个内部函数使用了外部函数的变量时,这个内部函数就会“捕获”这些变量,即使外部函数已经执行完毕,这些变量依然存在于内部函数的作用域中。

这种机制就是闭包的本质,无需任何额外的语法标识。闭包使得数据能够被封装在函数内部,同时允许通过返回内部函数来访问和操作这些私有数据。

闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。
也就是说,闭包让函数“记住”它创建时的作用域,即使函数执行完毕,外部变量依然被保留!

使它们不会被垃圾回收(GC)清除

闭包"封闭"了变量,让它们 不被销毁
即使外部作用域执行完毕,变量仍然活着

函数执行时,每个执行上下文中都会有一个包含其中变量的对象。全局上下文中的叫变量对象,它 会在代码执行期间始终存在

而函数局部上下文中的叫活动对象,只在函数执行期间存在。

函数内部的代码在访问变量时,就会使用给定的名称从作用域链中查找变量。函数执行完毕后,局 部活动对象会被销毁,内存中就只剩下全局作用域。不过,闭包就不一样了。

另一个有意思的副作用就是,createComparisonFunction()的 活动对象并不能在它执行完毕后销毁,因为匿名函数的作用域链中仍然有对它的引用。

在 create- ComparisonFunction()执行完毕后,其执行上下文的作用域链会销毁,但它的活动对象仍然会保留 在内存中,直到匿名函数被销毁后才会被销毁:

// 创建比较函数  
let compareNames = createComparisonFunction('name');

// 调用函数  
let result = compareNames({ name: 'Nicholas' }, { name: 'Matt' });

// 解除对函数的引用,这样就可以释放内存了 
compareNames = null;
谨慎使用闭包 因为占他妈的太多内存了

在 JavaScript 里,每次调用一个函数,都会创建一个新的执行上下文(Execution Context),包括新的作用域(Lexical Environment)

闭包里的 this

如果内部函数没有使用箭头函数定义,则 this 对象会在运 行时绑定到执行函数的上下文。如果在全局函数中调用,则 this 在非严格模式下等于 window,在严 格模式下等于 undefined。如果作为某个对象的方法调用,则 this 等于这个对象。匿名函数在这种情 况下不会绑定到某个对象,这就意味着 this 会指向 window,除非在严格模式下 this 是 undefined。

函数内部的 - 函数上下文

全局的 - Window/undefined

对象的方法的 - 对象

匿名的 - Window/undefined

内存泄漏

IE 不支持导致的

立即调用 IIFE

<span style="font-size: 30px;"></span>

在 ECMAScript 6 以后,IIFE 就没有那么必要了

因为块级作用域中的变量无须 IIFE 就可以实现同 样的隔离。

(function() { // 块级作用域

})();
  // IIFE
    (function () {

      for (var i = 0; i < count; i++) {
        console.log(i);

} })();

console.log(i); // 抛出错误 i只在内部

// 内嵌块级作用域 {

  let i;
  for (i = 0; i < count; i++) {

    console.log(i);
  }

}  
console.log(i); // 抛出错误

// 循环的块级作用域  
for (let i = 0; i < count; i++) {

  console.log(i);
}

console.log(i); // 抛出错误

私有变量

严格来讲,JavaScript 没有私有成员的概念,所有对象属性都公有的。不过,倒是有私有变量的概 念。

任何定义在函数或块中的变量,都可以认为是私有的

function MyObject() {  
// 私有变量和私有函数  
let privateVariable = 10;

      function privateFunction() {
        return false;

}

// 特权方法  
this.publicMethod = function() {

        privateVariable++;

        return privateFunction();
      };

}
name = value; 

function Person(name) {
  this.getName = function() {

  return name;
};

this.setName = function (value) {

}; }

let person = new Person('Nicholas');
console.log(person.getName());  // 'Nicholas'
person.setName('Greg');
console.log(person.getName());  // 'Greg'

此时只能通过特定方法访问 Person 的属性/变量 保护起来了

注意 使用闭包和私有变量会导致作用域链变长,作用域链越长,则查找变量所需的时间 也越多。