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响应式原理用到
set
和get
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);