重学JavaScript高级(五):JavaScript函数和对象知识增强

本文详细探讨了JavaScript函数的关键概念,包括函数的name属性、length属性、arguments对象的使用、纯函数、副作用、柯里化和组合化函数,以及严格模式和对象属性的控制。特别提到了Vue2中的响应式原理应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JavaScript函数知识增强

函数属性和arguments

  • name属性(用的比较少)
//就是为了区分函数的名字
function foo(){
    
}
function foo1(){
    
}

console.log(foo.name)//foo
console.log(foo1.name)//foo1
  • length属性:形参的个数
    • 注意:剩余参数…args不在统计范围之内
    • 注意:默认值的参数不在统计范围之内
function foo(a,b,...args){
    
}//foo.length = 2

function foo1(a,b=10){
    
}//foo1.length = 1

function foo2(){
    
}//foo2.length = 0

arguments的使用

arguments是一个类数组对象

function foo1(){
    arguments = [1,2,3,4]
}
foo1(1,2,3,4)
  • array-like意味着它不是一个数组类型,而是一个对象的类型
    • 但是它却拥有数组的特性,比如length,可以通过index进行访问
    • 但是没有数组的方法,比如filter、map等

arguments转成Array

  • 目的是为了使用数组中的方法

  • 方法一

    let newArg = []
    for(let arg of arguments){
        newArg.push(arg)
    }
    
  • 方法二:通过ES6的做饭,传入的一定是类数组对象

    let newArg = Array.from(arguments)
    
  • 方法三

    let newArg = [...arguments]
    
  • 方法四,调用slice方法

    slice对数组进行截取

    let newArg = [].slice.apply(arguments)
    let newArg = Array.prototype.slice.apply(arguments)
    

箭头函数不绑定arguments

  • 因为箭头函数中没有arguments,所以在里面使用的时候,会在上层作用域中查找
let bar = () => {
  console.log(arguments)
}
bar()
//以上代码会报错,因为全局都没有arguments
function foo() {
    let bar = () => {
        console.log(arguments)
    }
    bar()
  }
//打印的arguments是foo中的

函数的剩余参数(数组)

  • ES6中引入了剩余参数的概念,使用…进行接收
  • 剩余参数要么 单独写,要么写到 所有参数的最后
function foo(num1,num2,...arg){
    
}
foo(10,20,30,40)
//会将30,40传入到arg中
  • 剩余参数只包含没有对应形参的实参,而arguments对象*包含了所有的参数
  • 剩余参数就是为了替代arguments而存在的

JavaScript纯函数

  • 函数式编程中有一个非常重要的概念叫纯函数,JS符合函数式编程

    • 在eact开发中纯函数被多次提及
    • 比如 react中组件就被要求像是一个纯函数,redux中有一个reducer的概念,也是要求必须是一个纯函数
    • 所以掌握纯函数对于理解很多框架的设计是非常有帮助的
  • 纯函数在维基百科中的定义

    • 此函数在相同的输入值时,需产生相同的输出
    • 函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I/0设备产生的外部输出无关(意思就是输入和输出的内容,不能和其他的任何因素有关系
    • 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等
  • 个人理解

    • 确定的输入,一定会产生确定的输出

    • 函数在执行的过程中,不能产生副作用

    • 且在函数执行过程中,不能依赖外部变量

      let a = 100
      
      function add(num){
          return a+num//依赖了外部变量,所以不是纯函数
      }
      

副作用本身是医学方面的概念,比如吃药的对身体产生了一些副作用

在计算机科学中,也引用了副作用的概念,表示在执行一个函数的时候,除了返回值之外还对别的参数、变量等内容进行了更改

function foo(obj){
    console.log(obj.name)//在这里的时候还是纯函数,没有对任何参数产生副作用
    obj.a = 100;//但是这里就对传入的参数产生了副作用
}

副作用 是产生bug的温床

  • 纯函数的作用和优势
    • 最主要的就是可以 安心的编写代码和安心的使用代码
    • 安心的编写:在实现函数的时候,我们不需要关心外层作用域的值,就可以实现函数的逻辑
    • 安心的用:我们在调用纯函数的时候,不用担心有任何的副作用
    • React中就要求我们无论是 **函数还是class声明的一个组件,**这个组件都必须像 纯函数一样,保护它们的props不被修改

柯里化概念(用的不是很多)

  • 柯里化也是属于函数式编程的概念
    • 是一种关于函数的高阶技术
  • 维基百科的解释
    • 在计算机科学中,柯里化(Currying),又称为卡瑞化等
    • 是把接收多个参数的函数,变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数,而且返回结果的新函数的技术;
  • 对定义的理解
    • 传递函数的一部分参数来调用它,让 它返回一个函数去处理剩余的参数
    • 这个过程就是柯里化
  • 柯里化是一种函数的转换,将一个函数f(a,b,c)转换为f(a)(b)©
    • 柯里化不会调用函数,只是对函数进行转换
//未柯里化的函数
function foo(a, b, c) {
  console.log(a, b, c);
}

foo(10, 20, 30);

//柯里化之后的函数
function foo(a) {
  return function (b) {
    return function (c) {
      console.log(a, b, c);
    };
  };
}

foo(10)(20)(30);

//柯里化函数的箭头函数写法:需要了解箭头函数的几个规则,前面的文章有介绍过
let foo = (a) => (b) => (c) => console.log(a, b, c);

组合化函数的概念

  • 组合(Compose)函数是在JavaScript开发过程中一种对函数的使用技巧、模式
    • 比如我们现在需要对某一个数据进行函数的调用,执行两个函数fn1和fn2,这两个函数是依次执行的
    • 那么如果每次我们都需要进行两个函数的调用,操作上就会显得重复
    • 我们可以将两个函数组合起来,进行调用,这个过程就是 对函数的组合
//现在需要对一个数先乘2,在对乘2之后的数平方
function double(num) {
  return num * 2;
}

function pow(num) {
  return num ** 2;
}

console.log(pow(double(2)));

//这个就是组合函数
function composeFun(num) {
  return pow(double(num));
}

console.log(composeFun(2));

  • 同时我们也可以将其封装成一个工具函数
function composeFun2(...fns) {
  //对传入的函数可以进行边界的判断
  //比如...fns的长度为0,传入的不是函数等等
  if (fns.length === 0) return;
  for (let i = 0; i < fns.length; i++) {
    if (typeof fns[i] !== "function") {
      throw new Error(`index ${i} 不是函数`);
    }
  }
  //...arg接收的就是要处理的数据
  return function (...arg) {
    //我们可以将数据传给第一个函数执行,将其返回结果再一次执行
    let result = fns[0].apply(this, arg);
    //以下依次运行传进来的函数
    for (let j = 1; j < fns.length; j++) {
      result = fns[j].apply(this, [result]);
    }
    return result;
  };
}
let res = composeFun2(double, pow);
console.log(res(2));

with语句使用(了解,开发中不建议写)

  • with扩展一个语句的作用域链
let obj = {
    name:"zhangcheng"
}
console.log(name)//直接报错
with(obj){
    //打印出来zhangcheng
    console.log(name)
}

eval函数(了解)

  • 内建函数eval允许执行一个代码字符串
let stringEval = `console.log(123)`
eval(stringEval)
  • 不建议在开发中使用
    • eval代码的可读性差
    • eval执行的是一个字符串,有可能在执行的过程中,被可以篡改,可能造成被攻击的风险
    • eval的执行必须经过JS解释器,不能被JS引擎优化

严格模式

认识严格模式

  • JavaScript历史的局限性
    • JavaScript是不断向前发展的,但是在发展的历程中,没有带来任何的兼容性问题
    • 这是因为 新的特性被加入的同时,旧的功能没有被删除
    • 但是这样做的缺点就是 语言历史性的错误会一直被保留
  • 在ES5中突出了 严格模式的概念
    • 是一种 具有限制性的JavaScript模式,从而使代码隐式的脱离了 ”懒散 (sloppy)模式
    • 支持严格模式的浏览器在检测到代码中有严格模式时,会以更加严格的方式对代码进行检测和执行
  • 严格模式对正常的JS语义进行了一些限制
    • 严格模式通过 抛出错误 来消除一些原有的 静默 (silent) 错误
    • 严格模式让JS引擎在执行代码时可以进行更多的优化(不需要对一些特殊的语法进行处理)
    • 严格模式禁用了在ECMAScript未来版本中可能会定义的一些语法

开启严格模式

  • 给整个js文件开启严格模式

    "use strict"
    
  • 给单独一个函数开启严格模式

    function foo(){
        //一定要写在函数的开头
        "use strict"
    }
    

严格模式的限制(常见的)

  • JavaScript被设计为新手开发者更容易上手,所以有时候本来错误语法,被认为也是可以正常被解析的;

  • 但是这种方式可能给带来留下来安全隐患

  • 在严格模式下,这种失误就会被当做错误,以便可以快速的发现和修正

  • 常见的限制

    • 1.无法意外的创建全局变量

      "use strict"
      function foo(){
          message = "123"
      }
      console.log(message)//默认情况下,message是全局的变量,但是严格模式下会报错
      
    • 2.严格模式会使引起静默失败(silently fail,注:不报错也没有任何效果)的赋值操作抛出异常

    • 3.严格模式下试图删除不可删除的属性

    • 4.严格模式不允许函数参数有相同的名称

    • 5.不允许0的八进制语法

    • 6.在严格模式下,不允许使用with

    • 7.在严格模式下,eval不再为上层引入变量

      "use strict"
      let stringEval = `let a = 123`
      eval(stringEval)
      //变量a是访问不到的
      
    • 8.严格模式下,this绑定不会默认转成对象

JavaScript对象知识增强

对象属性的控制(很少用,但是会用在vue2响应式原理)

  • 默认情况下,属性没有进行特别的限制
  • 如果我们像要对 **对象中的一个属性进行比较精准的操作控制,**那么我们就可以使用 属性描述符
    • 通过属性描述符 可以精准的添加或修改对象的属性
    • 属性描述符需要使用 Object.defineProperty来对属性进行添加或者修改
let obj = {
    a:100
}

//三个参数:对象,属性,具体描述
Object.defineProperty(obj,prop,descriptor)

属性描述符的分类

分为两类:数据属性描述符和存取属性描述符

数据属性描述符

  • configurable:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符

    • 当我们通过属性描述符定义一个属性时,这个属性的[[Cnfigurable]]默认为false;
  • enumerable:表示属性是否可以通过for-in或者Object.keys()返回该属性值

    • 当我们通过属性描述符定义一个属性时,这个属性的[[Enumerable]]默认为false
  • writable:表示是否可以修改属性值

    • 当我们通过属性描述符定义一个属性时,这个属性的[[Writable]]默认为false
  • value:读取属性的时候,就会返回设置的值

    • 默认是undefined
let obj = {
  a: 100,
};

Object.defineProperty(obj, "a", {
  configurable: false,//告诉js引擎,a属性不可被删除
  enumerable: false,//告诉js属性,obj对象name不可被枚举
  writable: false,//告诉js引擎,obj对象的属性不可写入(只读)
  value: "zhangcheng",//告诉js引擎在读取该属性的时候,返回zhangcheng
});

存储属性描述符

vue2响应式原理用到

  • setget
let obj = {
  a: 100,
};
let _temp = "";
Object.defineProperty(obj, "a", {
  set: function (value) {
    console.log("set方法被调用了", value);
    _temp = value;
  },
  get: function () {
    console.log("get方法被调用了");
    return _temp;
  },
});

obj.a = "123";
console.log(obj.a);

同时给多个属性设置描述符

Object.defineProperties进行设置

let obj = {
  a: 100,
  b: 200,
};
let _temp = "";

Object.defineProperties(obj,{
    key1:{
        
    },
    key2:{
        
    }
})


Object.defineProperties(obj, {
  a: {
    set: function (value) {
      console.log("a的set方法被调用了", value);
      _temp = value;
    },
    get: function () {
      console.log("get方法被调用了");
      return _temp;
    },
  },
  b: {
    set: function (value) {
      console.log("b的set方法被调用了", value);
      _temp = value;
    },
    get: function () {
      console.log("get方法被调用了");
      return _temp;
    },
  },
});

obj.a = "123";
console.log(obj.a);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值