JavaScript函数
1.1JScontinue语句定义和用法:continue 用于跳过循环中的一个迭代,并继续执行循环中的下一个迭代。continue 与 break 语句的区别是:break 是结束整个循环体,continue是结束单次循环。但是,在执行 continue 语句时,表现出了两种不同类型的循环:在 while 循环中,会先判断条件,如果条件为 true,循环再执行一次。在for循环中,自增长表达式(
1.1 JS continue 语句
定义和用法:
-
continue 用于跳过循环中的一个迭代,并继续执行循环中的下一个迭代。
-
continue 与 break 语句的区别是:break 是结束整个循环体,continue是结束单次循环。
但是,在执行 continue 语句时,表现出了两种不同类型的循环:
-
在 while 循环中,会先判断条件,如果条件为 true,循环再执行一次。
-
在for循环中,自增长表达式(如:i++)会先计算,然后再判断条件是否为true,再决定是否执行迭代。
continue 语句可应用于可选的标签引用。
注意: continue 语句(不带标签引用),只能用在循环或 switch 中。
循环代码块,在 i 的值等于 "3" 时跳过当前循环:
2.1 JS break 语句
定义和用法:
-
循环代码块,break在变量 i 为 "3" 时退出循环:
-
break 语句用于退出 switch 语句或循环语句(for, for ... in, while, do ... while)。
-
当 break 语句用于 switch 语句中时,会跳出 switch 代码块,终止执行代码。
-
当 break 语句用于循环语句时,会终止执行循环,并执行循环后代码(如果有的话)。
-
break 语句同样可用于可选的标签引用,用于跳出代码块。
注意: break 语句(不带标签引用),只能用在循环或 switch 中。
3.1 JS arguments 对象
定义和用法:
arguments是函数里的 | 可以通过数组下标的形参访问函数实参值 |
arguments对象 | 没有表示参数集合,而是一个伪类数组 |
arguments对象是 | 所有(非箭头)函数中都可用的局部变量 |
此对象包含传递给函数的每个参数 | 第一个参数在索引0处。 |
你可以使用arguments对象在函数中引用函数的参数。 |
例如,如果一个函数传递了三个参数,你可以如下方式引用他们:
-
arguments[0]
-
arguments[1]
-
arguments[2]
求平均值:
function avg() {
var num = 0, j = 0;
for (let i = 0; i <= arguments.length; i++) {
if (typeof arguments[i] != 'number')
continue;
num += arguments[i];
j++;
}
num /= 1;
return num;
}
document.write(avg(1, 2, 3, 4));
3.1.1 callee回调函数
它如果和callee属性一起使用的话,利用它可以设计函数迭代操作。使用arguments.callee可以获取匿名函数。最后比较实参和形参的
个数以检测用户传递的参数是否符合要求。如果不是匿名函数的话,则arguments.callee等价于函数名(f());
语法:
[function.]arguments.callee
可选项 function 参数是当前正在执行的 Function 对象的名称。
说明:
callee 属性的初始值就是正被执行的 Function 对象。
callee 属性是 arguments 对象的一个成员,是arguments的属性,该属性是一个指针,指向拥有arguments对象的函数,这有利于匿
名函数的递归或确保函数的封装性,例如下边示例的递归计算1到n的自然数之和。而该属性仅当相关函数正在执行时才可用。更有需要注意
的是callee拥有length属性,这个属性有时候用于验证还是比较好的。arguments.length是实参度,arguments.callee.length是形参长
度,由此能够判断调用时形参长度是否和实参长度一致。
function calleeDemo() {
alert(arguments.callee);
}
3.1.2 throw new Error()方法:在控制台里抛出错误提示。
throw 语句允许我们创建自定义错误。正确的技术术语是:创建或抛出异常(exception)。
如果把 throw 与 try 和 catch 一起使用,那么您能够控制程序流,并生成自定义的错误消息。异常可以是 JavaScript 字符串、数字、
逻辑值或对象。
throw new Error('传递的参数不匹配');
3.1.3 search() 搜索
search() 方法 | 用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串 |
search() 方法 | 执行正则表达式和 String 对象之间的一个搜索匹配 |
当你想要知道字符串中是否存在某个模式(pattern)时可使用 search(),类似于正则表达式的 test() 方法。当要了解更多匹配信息
时,可使用 match()(但会更慢一些),该方法类似于正则表达式的 exec() 方法。
语法:
stringObject.search(regexp)
参数 | 描述 |
regexp | 该参数可以是需要在 stringObject 中检索的子串,也可以是需要检索的 RegExp 对象。 注释:要执行忽略大小写的检索,请追加标志 i。 |
返回值
stringObject 中第一个与 regexp 相匹配的子串的起始位置。
注释:如果没有找到任何匹配的子串,则返回 -1。
说明:
search() 方法不执行全局匹配,它将忽略标志 g。它同时忽略 regexp 的 lastIndex 属性,并且总是从字符串的开始进行检索,这
意味着它总是返回 stringObject 的第一个匹配的位置。
特别说明:
-
字符串中字符的位置与数组类似,都是从0开始的。
-
searchValue不但可以是普通字符串,也可以是正则表达式,
function isEmail() {
if (arguments.length > 1) {
throw new Error('你只能传递一个参数');
}
var regexp = /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;
if (arguments[0].search(regexp) != -1) {
return true;
} else {
return false;
}
}
var email = '13021946513@qq.com';
if (isEmail(email)) {
document.write(email);
} else {
document.write('请您重新输入邮箱地址');
}
4.1 使用函数对象
4.1.1 获取函数形参个数
-
获取函数的实参个数:使用arguments对象的length属性,仅能够在函数体内使用。
-
获取形参的个数使用函数名.length或者a.callee.length,function对象的length属性在函数体内外都可以使用.
function check(a) {
if (a.length != a.callee.length) {
throw new Error('参数不一致');
}
}
function f(a, b, c, d) {
check(arguments);
return (a + b + c + d)
}
document.write(f(3, 4))
4.2.1 自定义属性
-
用户可以通过点语法为函数定义静态属性和方法。
-
函数属性可以在函数体内定义,只能在内部调用;函数属性也可以在函数体外定义,在外部调用。
-
函数方法可以在内部或外部定义,不管在内部或外部定义都可以调用此函数方法。
-
用户不妨为函数定义属性,然后利用函数属性实现函数每次返回低增值。
function f() {
return f.x++;
}
f.x = 0;
for (var i = 0; i < 10; i++) {
document.write(f());
}
4.3 call()和apply()函数
call()和apply()是function对象的原型方法。把一个函数当做一个方法绑定到指定对象上并进行调用。
具体用法如下:
function.call(thisobj,args...)
function.apply(thisobj,args)
其中参数thisobj表示指定的对象,参数args表示要传递给被调用函数的参数。而call()方法只能接受多个参数列表,apply()只能接受一个数组
或伪数组,数组元素将作为参数传递给被调用的函数。
代码如下:
function f(x, y) {
return x + y;
}
function o(a, b) {
return a * b;
}
document.write(f.call(o, 3, 4));
使用call()和apply()方法可以把一个函数转换为指定对象的方法,并在这个对象上临时绑定调用该方法。
提示:它们能够动态改变函数内this指代的对象,这在面向对象编程中是非常有用的。
4.3.1 call()和apply()区别如下:
-
call()和apply()传递给参数的方式不同,
-
apply()是以数组形式传递参数,
-
call()方法以多个值的形式传递参数。
代码如下:
function r(x) {
return (x);
}
function f(x) {
x[0] = x[0] + '>';
return x;
}
function o() {
var temp = r;
r = function () {
return temp.apply(this, f(arguments));
}
}
function a() {
o();
document.write(r('='));
}
for (var i = 0; i < 10; i++) {
a();
}
4.4 使用bind()
函数function增加了一个原型方法bind(),是指用来把函数绑定到指定对象上。
具体用法如下:
function.bind(thisArg[,arg1[,arg2[,argN]);
参数说明如下:
function | 必须参数,一个函数对象 |
thisArg | 必须参数,this关键字可在新函数中引用的对象 |
arg1[,arg2[,argN]] | 可选参数,要传递到新函数的参数的列表。 |
bind方法将返回与函数相同的新函数不会立即执行,等需要的时候才会执行该新函数。bind()方法也可以多个或两个传递参数值。
var displayArgs = function (val1, val2, val3, val4) {
document.write(val1 + '<br> ' + val2 + ' <br>' + val3 + '<br> ' + val4 + '<br> ');
}
var emptyObject = {};
var displayArg2 = displayArgs.bind(emptyObject, '姓名:' + '张亮', '年龄:' + 25 + '岁');
displayArg2('身高:' + 180 + 'cm', '民族:' + ' ' + '汉族');
4.4.1 JavaScript trim() 方法
语法:
string.trim()
定义和用法:
trim() | 方法用于删除字符串的头尾空格。 |
trim() | 方法不会改变原始字符串。 |
实例:
去除字符串的头尾空格:
function myTrim(x) {
return x.replace(/^\s+|\s+$/gm,'');
}
function myFunction() {
var str = myTrim(" Runoob ");
alert(str);
}
5.1 使用this
this的定义:
this是函数体内自带的一个对象指针,this指向当前调用函数的那个对象,或者指向类的当前实例。
this的特点:
-
在function内部被创建
-
指向调用时所在函数所绑定的对象(拗口)
-
this 不能被赋值,但可以被 call/apply 改变
this的用途:这里把它所有用到的地方列出
- this 和构造器
function Tab(nav, content) {
this.nav = nav
this.content = content
}
Tab.prototype.getNav = function() {
return this.nav;
};
2. this 和对象
var tab = {
nav: '',
content: '',
getNav: function() {
return this.nav;
},
setNav: function(n) {
this.nav = n;
}
}
3. this 和函数
function showMsg() {
alert(this.message)
}
var m1 = {
message: '输入的电话号码不正确'
}
var m2 = {
message: '输入的身份证号不正确'
}
showMsg.call(m1) // '输入的电话号码不正确'
showMsg.call(m2) // '输入的身份证号不正确'
4. 全局环境的 this
var x = 10;
function func() {
alert(this.x)
}
var obj = {
x: 20,
fn: function() {
alert(this.x)
}
}
var fn = obj.fn
func() // 10
fn() // 10
没错,最终输出的都是全局的10。永远记住这一点:判断 this 指向谁,看执行时而非定义时,只要函数(function)没有绑定在对象上调用,它的
this就是window。
5. this 和 DOM/事件
<div id="nav" onclick="getId()">ddd</div>
<script>
function getId() {
alert(this.id)
}
</script>
点击 div 后,为什么 id 是 undefined,不说是指向的当前元素 div 吗?如果记住了前面提到的一句话,就很清楚为啥是 undefined,把这句话再贴出来。判断 this 指向谁,看执行时而非定义时,只要函数(function)没有绑定在对象上调用,它的 this 就是 window。
这里函数getId调用时没有绑定在任何对象上,可以理解成这种结构:
div.onclick = function() {
getId()
}
getId 所处匿名函数里的 this 是 div,但 getId 自身内的 this 则不是了。当然 ES5 严格模式下还是有个坑。
6. this 可以被 call/apply/bind 改变
var m1 = {
message: 'This is A'
}
var m2 = {
message: 'This is B'
}
function showMsg() {
alert(this.message)
}
showMsg() // undefined
showMsg.call(m1) // 'This is A'
showMsg.call(m2) // 'This is B'
7. me/self/that/_this 暂存 this
如果采用 OOP 方式写 JS 代码,无可避免的会用到 this,方法内会访问类的内部属性(字段),也可能会调用类的另一个方法。当类的方法内又有一个 function 时,比如浏览器端开发经常遇见的给 DOM 元素添加事件,这时如果事件处理器(handler)中的想调用类的一个方法,此时 handler 内的 this 是 dom 元素而非类的当前对象。这个时候,需要把 this 暂存,开发者发挥着自己的聪明才智留下了几种经典的命名 me, self, that, _this。
如:一般会在每个方法的第一句就把 this 暂存下来。
8. ES5 中新增的 bind 和 this
var modal = {
message: 'This is A'
}
function showMsg() {
alert(this.message)
}
var otherShowMsg = showMsg.bind(modal)
otherShowMsg() // 'This is A'
bind 只是 call/apply 的高级版,其它没什么特殊的。
9. ES6 箭头函数(arrow function) 和 this
var book = {
author: 'John Resig',
init: function() {
document.onclick = ev => {
alert(this.author) ; // 这里的 this 不是 document 了
}
}
};
book.init()
对象 book 里有一个属性 author, 有一个 init 方法, 给 document 添加了一个点击事件,如果是传统的函数,我们知道 this 指向应该是 document,但箭头函数会指向当前对象 book。
箭头函数让 JS 回归自然和简单,函数定义在哪它 this 就指向哪,定义在对象里它指向该对象,定义在类的原型上,就指向该类的实例,望文知意这样更容易理解。
总结:
函数的上下文 this 是 JS 里不太好理解的,在于 JS 函数自身有多种用途。目的是实现各种语言范型(面向对象,函数式,动态)。this 本质是和面向对象联系的,和写类,对象关联一起的, 和“函数式”没有关系的。如果你采用过程式函数式开发,完全不会用到一个 this。 但在浏览器端开发时却无可避免的会用到 this,这是因为浏览器对象模型(DOM)本身采用面向对象方式开发,Tag 实现为一个个的类,类的方法自然会引用类的其它方法,引用方式必然是用 this。当你给DOM对象添加事件时,回调函数里引用该对象就只能用 this 了。
六、使用闭包函数
闭包是Js的重要特性之一,在程序中有至关重要的作用。
Js函数能够记住自己定义时所处的作用域,即使函数不在此作用域运行,依然可以访问此作用域中的变量。
6.1认识闭包函数
闭包函数就是嵌套结构的函数。
闭包函数需要满足下面3个条件:
① 在一个函数(含有私有变量或局部变量)内定义的一个函数。也就是嵌套。
② 内部函数应该访问外部函数中声明的私有变量、参数或其他内部函数。
③ 如果在外部函数外调用这个内部函数,它就成为了闭包函数。
下面是一个经典闭包结构:
function f(x) {
var a = x;
var c=1;
var b = function () {
return a;//形成一个闭包
};
a++;
return b;
}
var c = f(5);
document.write(c());
------------------------
如果没有闭包函数的作用,这种数据寄存和传递无法实施:
function f(x) {
var a = x;
var b = a;
a++;
return b;
}
var c = f(5);
document.write(c);
当该函数时(f())不管是数字几赋值给c后进行调用,该是多少就是多少这个‘数’,不能实时监测这个变量的变化。
注意:一但函数被调用执行,则闭包体也会随之诞生。
闭包函数被再次执行或者通过某种方法进入函数体时,就可以获取闭包函数内包含的信息。
简单的描述:闭包可以说是函数的数据包、存储数据。这个数据包在函数执行过程中始终处于激活状态,只有调用函数闭包才能生效。当函数调用返回之后,闭包保存下来与内部函数关联的变量的动态联系(帮助监测)。
下面是闭包和闭包的常用场景:
-
作用域(受javascript链式作用域结构的影响,父级变量中无法访问到子级变量的值,为了解决这个问题,才使用的闭包。)闭包就是能够读取其他函数内部变量的函数。(在JS中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解为”定义在一个函数内部的函数”。无论是在事件中,for循环中,还是在函数自调用中,只要return出来,便有闭包的应用)。
-
闭包会把函数中变量的值保存下来,供其他函数使用,这些变量会一直保存在内存当中,这样占用大量的内存,使用不当很可能造成内存泄漏,故要及时清除,清楚方法有两种,一是标记清除,二便是引用计数清除。
-
闭包的常用场景有一是函数作为返回值,二是函数作为参数来传递。不适用于返回闭包的函数是个特别大的函数,很多高级应用都要依靠闭包实现.
-
使用闭包的好处是不会污染全局环境,方便进行模块化开发,减少形参个数,延长了形参的生命周期,坏处就是不恰当使用会造成内存泄漏。
6.2 使用闭包
下面通过几个示例介绍闭包的简单使用,更直观的理解什么是闭包以及闭包的函数的作用和用法。
用法1:使用闭包结构能够跟踪动态环境中的实时变化,并即时存储。
function f() {
var a = 1;
Var b = function () {
return a;
}
a++;
Return b;
}
var c=f();
document.write(c());
用法2:闭包不会因为外部函数环境的注销而消失,而是始终存在。
function f() {
var a = 1;
b = function () {
alert(a);
}
c = function () {
a++;
}
d = function (x) {
a = x;
}
}
<button οnclick="f()">按钮1:(f())</button><br>
<button οnclick="b()">按钮2:(b=function(){alert(a);})()</button><br>
<button οnclick="c()">按钮3:(c=function(){a++;})()</button><br>
<button οnclick="d(100)">按钮4:(d=function(x){a=x;})(100)</button><br>
用法3:如何利用闭包存储变量所有变化的值。
function f(x) {
var a = [];
for (var i = 0; i < x.length; i++) {
var temp = x[i];
a.push(function () {
document.write(temp + ' ' + x[i])
});
}
return a;
}
function e() {
var a = f(['a', 'b', 'c']);
for (var i = 0; i < a.length; i++) {
a[i]();
}
}
e();
6.3 定义闭包存储器
闭包常见的用法就是为要执行的函数提供参数。
比如:
为事件属性传递动作 |
为定时器函数传递行为等 |
这在web前端中非常常见的一种应用。
用法1:
function f(a, b) {
return function () {
a(b);
}
}
var c = f(alert, '爱前端');
var d = setTimeout(c, 1000);
闭包其实还可以用于创建额外的作用域,通过该作用域就可以设计动态数据管理器。
用法2:下面的例子如何使用闭包作为值来进行传递。
function f(a, b) {
return function () {
a(b);
}
}
var c = f(alert, '我在家学习和锻炼!');
window.onload = c;
用法3:下面通过闭包可以使作为缓冲器的数组与依赖它的函数关联起来。实施优雅的打包。
var f = function () {
var a = [1, 3, 5, 7, 9, 2, 4, 6, 8, 10];
return function (a1, a2, a3, a4, a5) {
a[0] = a1;
a[1] = a2;
a[2] = a3;
a[3] = a4;
a[4] = a5;
return a.join(',');
};
}();
var a = f(12, 14, 16, 18, 20);
var b = f(11, 13, 15, 17, 19);
document.write(a + '<br>');
document.write(b + '<br>');
6.4 在事件处理中应用闭包
很多时候我们希望引用一个函数后能暂停执行,因为在复杂的环境中不等到执行的时候是很难知道具体参数的,而先前被引用时更是无法预知所要传递的参数。
下面用法:希望为页面中特定的元素或标签绑定几个事件,使其能够在鼠标经过、鼠标移开和鼠标单击时呈现不同的背景颜色。
<p>p1</p> <p>p2</p> <p>p3</p>
<script>
function f(o, m) {
return function (e) {
e = e || window.event;
return o[m](e, this);
}
}
function g(id) {
return function () {
var e = document.getElementsByTagName(id);
if (e) {
for (var i in e) {
e[i].onclick = f(g, 'click');
e[i].onmouseover = f(g, 'over');
e[i].onmouseout = f(g, 'out');
}
}
}
}
g.click = function (event, element) {
element.style.backgroundColor = 'red';
}
g.over = function (event, element) {
element.style.backgroundColor = 'orange';
}
g.out = function (event, element) {
element.style.backgroundColor = 'green';
}
window.onload = g('p');
</script>
七、实战案例
7.1绑定函数
函数绑定就是为了纠正函数的执行上下文,特别是当函数中带有this关键字的时候,这一点尤其重要,稍微不小心,就会使函数的执行上下文发生跟预期不同的改变,导致代码执行上的错误。
函数绑定具有3个特征:
函数绑定要创建一个函数,可以在特定环境中以指定参数调用另一个函数。 |
一个简单的bind()函数接受一个函数和一个环境,返回一个在给定环境中调用给定函数的函数 |
并且将所有参数原封不动的传递过去。 |
被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重调用而稍微 慢一点,所以最好只在必要时使用。 |
var handler = {
messge: '学习js语言',
handlerClick: function (event) {
document.write(this.messge);
}
};
var btn = document.getElementsByTagName('my-btn');
EventUtil.addHandler(btn, 'clck', bind(handler.handlerClick, handler));
7.2 链式语法
因为 jQuery 库的缘故,(Jquery最大的亮点就是它的链式语法)链式语法在前端界变得非常流行。实际上这是一种非常容易实现的模式。基本上,你只需要让每个函数返回 'this',这样其他函数就可以立即被调用,它也是点语法。
下面用法:
Function.prototype.method = function (name, func) {
if (!this.prototype[name]) {
this.prototype[name] = func;
return this;
}
};
String.method('trom', function () {
return this.replace(/^\s+|\s+$/g, '');
});
String.method('writeln', function () {
document.write(this);
return this;
});
String.method('alert', function () {
console.log((this));
return this;
});
var str = '黄静远';
str.trim().writeln().alert();
7.3 函数节流
函数节流的设计思想就是让某些代码可以在间断情况下连续重复执行。实现的方法是使用定时器对函数进行节流。
下面用法:
function throttle(method, context) {
clearTimeout(method.tId);
method.tId = setTimeout(function () {
method.call(context);
}, 100);
}
需要注意的一点:函数的节流是通过减少实际逻辑处理过程的执行来提高事件处理函数运行性能的手段,并没有实质上减少事件的触发次数。
7.4 柯里化
柯里化又称为部分求值,柯里化是把接受多个参数的函数变换成接收一个单一的参数的函数。并且返回一个新函数。而这个函数新函数能够接收原函数的参数。
下面用法:
function add(a) {
// 疯狂的回调
return function(b) {
return function(c) {
return function(d) {
// return a + b + c + d;
return [a, b, c, d].reduce(function(v1, v2) {
return v1 + v2;
});
}
}
}
}
console.log(add(1)(2)(3)(4)); // 10
柯里化有3个常见作用:
-
参数复用
-
提前返回
-
延迟计算/运行
7.5 递归
-
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,
-
递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
-
一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
递归算法解决问题的特点:
-
递归就是方法里调用自身。
-
在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。
-
递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。所以一般不提倡用递归算法设计程序。
-
在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等,所以一般不提倡用递归算法设计程序。
任何有意义的递归总是两部分组成的:
递归调用 |
递归终止条件 |
7.5.1递归运算
递归运算在无限制的情况下,会无休止地自身调用。显然,程序不应该出现这种无休止的递归调用,而只应出现有限次数的有终止的调用。
为此一般在递归运算中结合if语句来进行控制。
递归算法的特点:
-
在函数过程中调用自身。
-
在递归过程中,必须有一个明确的条件判断递归的结束,既递归出口。
-
递归算法简洁但效率低,通常不作为推荐算法。
用法1 :看电影不知道在第几排,看电影时不清楚自己在第几排,可以通过问前一排的人来得知,进行加1即可。
function fn = (n) {
if(n< = 0) return '座位不存在'
if(n>1) {
return fn(n-1)+1
} else {
return 1
}
}
用法2 :走格子有多少走法,一共有n格,每步可以走1格或者2格,问一共有多少走法。
首先分解问题是第n格可以是前面n-1格走的,也可能是n-2格走的。所以fn(n) = f(n-1) + f(n-2)。
也要知道终止条件是只有1步,那么只有一步的可能情况是还有1格,也可能是还有2格。
function fn = (n){
if(n>2){
return fn(n-1) + fn(n-2)
} else if(n==2) {
return 2
} else {
return 1
}
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)