01、JS函数基础

1.1、函数定义

函数(方法)就是一段定义好的逻辑代码,函数本身也是一个object引用对象。三种函数构造方式:

① 函数申明function 函数名(参数){代码},申明函数有函数名提升的效果,先调用,后申明(和var申明提升类似,比var提升更靠前)。

② 函数表达式var func = function(参数){代码},定义变量指向函数,函数不需要命名。不过也可以像申明函数一样指定函数名,在其内部调用自己。

③ Function构造函数new Function(参数,代码),支持多个构造参数,前面为函数参数,最后一个为函数体,使用不多。

function func1(a) {
console.log(a);
}
var func2 = function(b){
console.log(b);
}
var func3 = new Function('c','console.log(c)');
//调用函数
func1(1);
func2(2);
func3(3);

注意:JS中没有方法重载,不允许相同的函数名,重名会被覆盖。

return 返回值

  • 通过return 返回值,并结束方法。
  • return,则默认返回undefined

1.2、参数argument

  • 参数可以不传,则为undefined,也可多传,没卵用(也不一定,arguments参数数组可以用)。
  • 参数不可同名,如果同名,则后面为准。
  • 形参与实参:函数定义的参数a为形参,调用时传入的数据3为实参。
  • 参数设置默认值几种方式:形参赋默认值(ES6)、参数验证赋值。
function func1(a="默认值") {    //一般推荐的方式
a=a?a:"默认值"; //参数为null、undefined、0、false、空字符值都会用默认值
a=a||"默认值"; //同上
console.log(a);
}
  • 参数的值传递和引用传递:取决于参数的类型,值类型传递副本,引用类型传递对象指针地址,操作的是同一个对象。这里需要理解JS里面的值类型、引用类型的基本原理。
function f1(n) {
n += 1;
}
let n = 100;
f1(n); //值传递:传入的是n的值副本,不影响原本n值
console.log(n); //100 n没有变 function f2(obj) {
obj.n += 1;
}
let nobj = { n: 100 };
f2(nobj); //引用传递,传入的是nobj地址指针,操作同一对象,对象是共享的
console.log(nobj); //{n: 101}
  • arguments:函数传入的实参都保存在arguments数组对象中,对于任意数量参数的方法就很有用。
function sum() {  //求和
let n = 0;
for (let i = 0; i < arguments.length; i++) {
n += arguments[i];
}
return n;
}
sum(1,2,3,108,594); //求和,支持任意个参数
  • 剩余参数(...theArgs):把不确定的参数放到一个数组里,前面是确定参数,最后一个形参以...开头表示其他剩余参数数组,既然是数组,当然就可以支持任意数量的参数了。
// 连接字符串,支持任意数量字符参数
function connect(separator, ...sargs) {
let str = "";
for (let i = 0; i < sargs.length; i++) {
str += sargs[i]?.toString() + separator;
}
return str.slice(0, -separator?.toString().length); //去掉结尾的分隔符
}
connect('--', 1, 2, 3, 'a', 'b', 'c'); //1--2--3--a--b--c

1.3、函数(变量)作用域

  • 局部变量:函数内的变量称为“局部变量”,函数里才有作用域的问题——不能被全局、其他函数访问。
  • 全局变量:全局变量可以被自由访问,包括函数内
  • 父函数变量:函数中定义的函数也可以访问在其父函数中定义的所有变量,和父函数有权访问的任何其他变量。简单来说就是函数变量作用域单向向下传递,子级可以访问父级的变量。
var num1 = 0;
function getScore() {
let num1 = 2, //和全局变量同名
num2 = 3;
function add() {
num3 = 5; //没用任何申明的局部变量,使用后自动变为隐式全局变量
return num1 + num2; //可以访问全局、父级的变量
}
return add();
}
getScore(); //5
console.log(num1, num3); //0 5

️注意

  • 全局、局部(方法内)变量同名,两者没有关系,函数内肯定就近用自己的了。
  • 没用任何申明的局部变量,使用后自动变为隐式全局变量,全局window莫名其妙就有了很多私生子,so,不要这样干!

1.4、()=>{ }箭头函数

箭头函数(IE)是一种简洁的函数表达式,顾名思义就是用箭头=>创建函数,它没有自己的thisargumentssuper。箭头函数总是匿名的,适用于那些需要匿名函数的地方。

语法规则:(param1, param2, …, paramN) => { ... }

let add1 = (a, b) => { return a + b; };
let add = (a, b) => a + b; //同上,只有一行代码可以省略花括号{} 和 return
let logDate = () => { console.log(new Date()) }; //可以没有参数
let logError = e => { console.log('发生错误:', e) }; //一个参数可以省略括号

=>箭头函数和普通函数有什么区别呢?这是考点:

区别 描述
申明方式不同 普通函数需要function关键字,箭头函数当然就是箭头=>
没有自己的arguments 箭头函数在全局环境中,没有arguments;当箭头函数处于普通函数的中,arguments是上层普通函数的arguments
没有自己的this 没有自己的this,其this指向其函数定义的外层作用域环境的this,且不能被call、apply、bind函数改变。
没有自己的prototype 箭头函数没有自己的原型,加上没有自己的this,所以也就不能作为构造函数使用
// 箭头函数
let f1 = (a, b) => {
console.dir(f1.prototype); //undefined
console.log(arguments); //arguments is not defined
}
// 普通函数,嵌套了一个箭头函数
let f2 = function (a) {
console.dir(f2.prototype); //Object
// 嵌套的箭头函数
let f1 = (a, b) => {
console.log(arguments);
} //是父级f2的arguments
f1(a, 2);
}
//this
var name = '大哥';
let user = {
name: 'sam',
sayHi1: function () {
console.log('Hi', this.name)
},
sayHi2: () => { console.log('Hi', this.name) },
}
user.sayHi1(); //Hi sam :this指向调用者
user.sayHi2(); //Hi 大哥 :this指向定义的环境,全局对象
// 指定新的this
let nobj={name:'张三'};
user.sayHi1.call(nobj); //Hi 张三 :this指向绑定对象
user.sayHi2.call(nobj); //Hi 大哥 :this指向全局对象,始终没变

1.5、全局函数

函数 描述
eval() 执行JS代码(不推荐):eval("console.log('eval')");
- 比较危险,它使用与调用者相同的权限执行代码,字符串代码容易被被恶意修改。
- 效率低,它必须先调用JS解释,也没有其他优化,还会去查找其他JS代码(变量)。
- 推荐用 Function() 构造函数代替eval()
isNaN() 判断一个值是否是非数值
parseFloat() 转换字符为浮点数
parseInt() 转换字符为整数
decodeURI() URL解码
encodeURI() URL编码
alert(str) 弹窗消息提示框
confirm(str) 弹窗消息询问确认框,返回boolean
console 控制台输出
console.log(str) 控制台输出一条消息
console.error(str); 打印一条错误信息,类似的还有infowarn
console.dir(object) 打印对象
console.trace() 输出堆栈
console.time(label) 计时器,用于计算耗时(毫秒):time(开始计时) > timeLog(计时) > timeEnd(结束)
console.clear() 清空控制台,并输出 Console was cleared。
let arr = eval('[1,2,3]'); //转换字符串为数组
let jobj = eval("({name:'sam',age:22})"); //转换字符串为JSON对象
let jobj2 = new Function("return {name:'sam',age:22}")(); //转换字符串为JSON对象
//计时time,需一个统一标志
console.time("load");
console.timeLog("load"); //load: 5860ms
console.timeLog("load"); //load: 18815ms
console.timeEnd("load"); //load:25798 毫秒 - 倒计时结束

02、函数调用/call/apply/bind

常用的函数调用方式

  • 直接函数名调用:函数名(参数...);
  • 对象调用:对象里的函数,对象.函数名(参数...);
  • 递归调用,嵌套调用自身,须注意退出机制,避免死循环,代码的世界没有天荒地老。

不常用函数调用方式:call/apply/bind 调用函数都可以指定this值。

属性/方法 描述 语法
call() 调用函数,指定this、参数 function.call(thisArg, arg1, arg2, ...)
apply() 调用函数,指定this、参数数组 function.apply(thisArg, argsArray)
bind() 绑定(复制)一个函数,指定this、参数 function.bind(thisArg, arg1, arg2, ...)

call()

通过 Function.prototype.call() 调用一个函数,调用语法:

function.call(thisArg, arg1, arg2, ...)

  • 第一个参数thisArg指定运行时this,当第一个参数为null、undefined的时候,默认指向window。这一点可用来实现函数的“继承”(在构造函数中调用父构造函数)
  • 后面为函数原本的参数。
function sum(n1, n2) {
return n1 + n2;
}
sum(1,2); //正常调回
sum.call(null, 1, 2); //call调用
console.log(Math.max(1, 2)); //正常调回
console.log(Math.max.call(null, 1, 2)); //call调用

apply()

通过 Function.prototype.apply() 调用函数,调用语法:

function.apply(thisArg, argsArray)

call()的唯一区别就是第二个参数是一个参数数组,数组参数会分别传入原函数。

function sum(n1, n2) {
return n1 + n2;
}
sum.apply(null, [1, 2]); //apply调用
console.log(Math.max.apply(null, [5, 4, 2, 1, 22, 9])); //apply调用,数组传入多个参数 //绑定this
var uname = "sam";
let f = function () {
console.log(this.uname);
}
f(); //sam
f.call({ uname: "call" }); //call
f.apply({ uname: "apply" }); //apply

bind()

通过 Function.prototype.bind() 创建一个副本函数:该副本函数绑定了this和参数,且一经绑定,永恒不变(不可更改,不可二次绑定)。

function.bind(thisArg[, arg1[, arg2[, ...]]])

function log(type, title, message) {
console.log(`type:${type}, title:${title}, message:${message}`)
}
let errorLog = log.bind(null, '错误'); //返回绑定的函数,绑定第一个参数
errorLog('登录发生异常', '超过登录次数');

03、函数闭包

3.1、什么是闭包?

闭包函数申明该函数的词法环境的组合,简单来说能够访问其他函数(通常是父函数)内部变量的函数,加上他引用的外部变量,组成了闭包。通常就是嵌套函数,嵌套函数可以”继承“父函数的参数和变量,或者说内部函数包含外部函数的作用域。

  • 内部函数+外部引用形成了一个闭包:它可以访问外部函数的参数和变量。闭包存储了自己和其作用域的变量,这样在函数调用栈上才能使用外部函数的变量。
function FA(x) {
function FB(y) {
function FC(z) {
console.log(x + y + z);
}
FC(3);
console.dir(FC)
}
FB(2);
console.dir(FB)
}
FA(1); //6
console.dir(FA)

作用域链(C>B>A):B和A形成闭包,B可以访问A,保存了A的变量;C和B形成闭包,可以访问B(也包括B有的A作用域),如下图FC函数形成的闭包中存储了FB、FA的变量、参数信息。

因此,闭包就是为了解决了函数的词法作用域问题,FC函数就可以单独使用了。V8引擎是把闭包封装成了一个"Closure"对象,保存函数上下文中的[[Scope]]集合里。同一个函数多次调用都会产生单独的闭包,如果闭包使用不当或太多,容易引发内存泄漏风险。

JS设计闭包这个东西,一言难尽!详见后续《函数执行上下文》

3.2、闭包应用:柯里化(Currying)

柯里化是一种函数的高级玩法,简单来说,就是把多参数的函数f(a,b) ,转换成了另一种函数形式 f(a)(b)

//一个普通的日志函数
function print(title, message) {
console.log(title, message);
}
//柯里化转化函数
function curry(func) {
return function (title) {
return function (message) {
return func(title, message);
}
}
}
//柯里化转化
let cprint = curry(print);
//调用
cprint('用户模块')('用户1登录了');
//复用:复用包含了title参数值的函数。
let userPrint = cprint('用户模块');
userPrint('用户1退出登录');
userPrint('用户3打赏了游艇');

原理其实不难理解,就是利用闭包的(词法作用域)机制,返回多层闭包函数,直到最后一个参数来了才执行。这么做到底有什么好处?——答案就是复用,复用参数值。如上面示例中的userPrint,是一个包含了title参数值的(闭包)函数,他还有一个正式的名字,叫偏函数

一个更通用的柯里化实现如下,采用递归的方式,不仅可以生成任意偏函数,还支持正常调用。

function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function (...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
//使用
let log = curry(print);
log('登录模块','系统崩了'); //正常调用
let userLog = log('直播模块'); //偏函数-复用
userLog('表演才艺');
userLog('上链接');

04、this关键字

this 是JS的关键字,指向当前执行的环境对象,允许函数内引用当前环境的其他变量,不同场景指向不同。在严格模式("use strict";)下又会不同。大多数情况下,函数的调用方式决定了 this 的值(运行时绑定),每次调用函数的this也可能不同。

this.x1 = 100;
console.log(this.x1); //这里的this指向全局对象window function User() {
this.sname = "sam";
this.age = 20;
console.log(this.sname, this.age);
}
new User(); //sam 20,User构造函数里的this指向new创建的新User实例

this 指向的是一个对象引用,不是指向函数自身,也不是指向函数的词法作用域。大多数情况下默认都指向全局window

  • this=全局window:在全局执行环境中(在任何函数体外部)this 都指向全局对象window
  • this=new对象:构造函数中的this指向其新对象;对象的属性方法中的this指向该对象。
  • this=调用者:局部(函数内的)this,谁调用函数,this指的就是谁。箭头函数除外,箭头函数本身没有this,也不会接收call、apply的传递,指向其函数定义环境的this,而非执行时。
  • this=事件元素:在事件中,this表示接收事件的元素。
  • this=绑定对象call(thisArg)、apply(thisArg)、bind(thisArg)绑定参数thisArg作为其上下文的this,若参数不是对象也会被强制转换为对象,强扭的瓜解渴!
  • this=undefined:严格模式下,如果this没有被执行环境(execution context)定义,那this就是undefined
function Foo() {
console.log(this);//调用Foo(),this指向window;如果new Foo()则指向新对象
var fa = () => { console.log("fa:" + this) };
var fb = function () {
console.log("fb:" + this);
}
fa(); //箭头函数,调用Foo(),this指向调用者window;如果new Foo()则指向新对象
fb(); //匿名函数,调用Foo()、构造函数调用,this指向调用者window,
this.fc1 = fa; //属性方法:this指新对象
}
Foo(); //调用Foo()函数
new Foo(); //构造函数调用创新实例

又是一个JS的坑!好像懂了,又好像没懂!详见后续


️版权申明:版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处!原文编辑地址-语雀

JavaScript入门②-函数(1)基础{浅出}的更多相关文章

  1. JavaScript入门-函数function(二)

    JavaScript入门-函数function(二) 递归函数 什么是递归函数? 递归简单理解就是,在函数体里,调用自己. //我们在求一个10的阶乘的时候,可能会这么做 //写一个循环 var to ...

  2. 05.Javascript——入门函数

    //定义函数的方法1 function abs(x) { if (x >= 0) { return x; } else { return -x; } } 上述abs()函数的定义如下: func ...

  3. javaScript之函数(基础篇)

    函数定义 定义一: function a(x,y......){if(x>y){return x;}else{return y;}} 详细讲解:function是定义函数的关键字: a(x,y. ...

  4. JavaScript alert()函数的使用方法

    这里向大家简单介绍一下JavaScript alert()函数的使用,alert--弹出消息对话框,并且alert消息对话框通常用于一些对用户的提示信息. JavaScript alert()函数 a ...

  5. JavaScript入门基础

    JavaScript基本语法 1.运算符 运算符就是完成操作的一系列符号,它有七类: 赋值运算符(=,+=,-=,*=,/=,%=,<<=,>>=,|=,&=).算术运 ...

  6. 开心菜鸟系列----函数作用域(javascript入门篇)

      1 <!DOCTYPE html>   2 <html>   3 <script src="./jquery-1.7.2.js"></ ...

  7. javascript入门视频第一天 小案例制作 零基础开始学习javascript

    JavaScript 是我们网页设计师必备的技能之一.我们主要用javascript来写的是网页特效.我们从零基础开始学习javascript入门. 但是,好的同学刚开始不知道怎么学习,接触js,因此 ...

  8. 100多个很有用的JavaScript函数以及基础写法大集合

    100多个很有用的JavaScript函数以及基础写法大集合 1.document.write("");为 输出语句2.JS中的注释为//3.传统的HTML文档顺序是:docume ...

  9. Javascript&Ajax-深入浅出JSONP--解决ajax跨域问题

    Javascript&Ajax-深入浅出JSONP--解决ajax跨域问题 原理讲解: 链接地址:http://www.cnblogs.com/chopper/archive/2012/03/ ...

  10. 07.Javascript——入门高阶函数

    高阶函数英文叫Higher-order function..JavaScript的函数其实都指向某个变量.既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数 ...

随机推荐

  1. 基于electron+vue+element构建项目模板之【自定义标题栏&右键菜单项篇】

    1.概述 开发平台OS:windows 开发平台IDE:vs code 本篇章将介绍自定义标题栏和右键菜单项,基于electron现有版本安全性的建议,此次的改造中主进程和渲染进程彼此语境隔离,通过预 ...

  2. Elasticsearch:使用 IP 过滤器限制连接

    文章转载自:https://elasticstack.blog.csdn.net/article/details/107154165

  3. 2022.9.10-2022.9.12 Java第一次课总结

    本节课中的问题总结如下: 1.Java的基本运行单位是类还是方法? 答:Java的基本运行单位是类. 2.类由什么组成? 答:类由变量/方法/属性/事件等部分组成,其中方法就是我们所熟悉的函数,属性即 ...

  4. 大数据技术之HBase原理与实战归纳分享-中

    @ 目录 底层原理 Master架构 RegionServer架构 Region/Store/StoreFile/Hfile之间的关系 写流程 写缓存刷写 读流程 文件合并 分区 JAVA API编程 ...

  5. Windows活动目录_初识

    计算机组织形式 工作组(用户本地登录时构造SID进行权限分配): 域(统一身份验证与管理) 域注意事项 实体:域控.域用户.加入域的机子. 依赖的服务:netlogon服务 强制刷新组策略gpupda ...

  6. vue-router路由实现页面的跳转

    1.项目结构 2.定义组件 组件1 Login.vue <template> <form> 账号:<input type="text"> 密码: ...

  7. RedHat7.6安装mysql8步骤

    1.官网下载mysql安装包 直达链接:https://dev.mysql.com/downloads/mysql/    2.将下载好的安装包上传到redhat系统上(有多种上传方式,本次使用Sec ...

  8. SQL生成脚本

    右键要生成脚本的数据库 选择task 选择Generate script 选择需要生成脚本的table.view.procedure

  9. 三十四、kubernetes证书介绍

    Kubernetes 证书介绍 一.证书机制说明 Kubernetes 作为一个分布式集群的管理工具,保证集群的安全性是其一个重要的任务.API Server 是集群内部各个组件通信的中介,也是外部控 ...

  10. 图数据 3D 可视化在 Explorer 中的应用

    本文首发于 NebulaGraph 公众号 前言图数据可视化是现代 Web 可视化技术中比较常见的一种展示方式,NebulaGraph Explorer 作为基于 NebulaGraph 的可视化产品 ...