10函数
1.定义
通俗来讲,函数就相当于一个工厂,将某些东西放进去加工后,再吐出来加工后的产物,用来实现某个功能。函数是对象,函数名是指针。
关于如何理解函数名是指针:
<个人理解就是看内存中保存的是具体内容还是地址。>
var num = 123; var num_a = num; console.log(num, num_a);//123 123 //此时内存中有两个123
var obj = {nickname:'宠儿'}; var obj_a = obj; console.log( obj, obj_a);//nickname:'宠儿' nickname:'宠儿' //此时内存中只有一个宠儿
2.声明
function some(){
console.log(1);//在后边加括号不报错的都是函数
}
//①函数声明:
function sum (num1 , num2){
return num1 + num2;
}
//②用函数表达式定义函数(不推荐<--两次解析代码,影响性能,不会被提升到顶层)
var sum = function(num1 +num2){
//定义了变量sum并将其初始化为一个函数
return num1 + num2;
};//注意这里的分号还要写,就像声明其他变量一样
函数声明与函数表达式的区别:
解析器在向执行环境中加载数据时,会率先读取函数声明(函数提升)。并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。
比如以下例子,并不会影响代码的正常的运行
alert(sum(10,10));
function sum (num1,num2){
return num1 + num2;
}
而下边这个,就会报错: Uncaught TypeError: fn_b is not a function at XXX
fn_b();
var fn_b = function () {console.log( '我是函数fn_b' )};
3.调用
function some(){
console.log('1');//此时控制台上并不会打印出1
}
some();//此时运行some函数里边的代码
function some2(abc){
console.log(abc);
}
//函数可以无数次调用
some2 = ('露娜');
some2 = ('公孙离');
4.参数
函数的参数是按值传递的
实际参数:传递给函数的东西
形式参数:函数中的变量,可以有无数个。形参和实参是一一对应的
保存函数参数:
arguments
(除了箭头函数外,任何函数都有这个属性)。
- 函数在定义时没有规定参数, 函数在执行的时候也可以往里面传入若干个参数, 参数按照顺序存储在
arguments
数组中arguments
是一个类数组对象,包含着传入函数中的所有参数,arguments
拥有一个名叫callee
的属性,该属性是一个指针,指向拥有这个arguments
对象的函数。当函数在严格模式下运行时,访问arguments.callee
会导致错误。
- 严格模式:
script
首行或者在函数体内写"use strict"
//实参
function some1(){
console.log('我是some1函数');
}
//形参
function factory(abc){//abc就是形参
var abc = '皮皮虾';
console.log(abc);
}
//arguments
var a = 10, b = 20;
function fn(x,y){
console.log(arguments[0]);
}
fn(a,b);
arguments
应用示例:加法运算
function sum_a(){
var result = 0,
for( var i = 0,length = arguments.length; i < length; i++){
if( typeof arguments[i] === 'number' ){
result += arguments[i];
}
}
console.log(result)
}
sum_a(1,1,2,3,456,645,56);
arguments
应用示例:阶乘函数
function factorial(num) {
if (num <= 1){
return 1;
}else{
return num * arguments.callee(num-1);
//等同于-->num * factorial(num-1);(弊端:函数执行与函数名紧密耦合)
//用到了递归
}
}
5.分类
具名函数:有名字的函数
匿名函数:没有名字的函数
//具名函数:
function functionName(){
console.log("我是个名字为functionName的函数");
}
functionName();//利用名字调用
//匿名函数:
var obj = {
abc :function (){//此时括号前没有名字哦,即使有名字,也无法使用,没有存在的价值
console.log('我是obj对象里的abc属性,我是一个函数');
}
}
obj.abc();
6.功能–>封装
以之前学过的冒泡排序为例,就是将整个冒泡排序这种算法装进一个函数里,方便以后使用。
//将整个算法装进名为sort的函数中:
function sort (arr) {//sort[排序]
for( var i = 0, length = arr.length; i < length - 1; i++ ){
for( var k = 0; k < length - 1 - i; k++ ){
if( arr[k] < arr[k+1] ){
var little = arr[k]
arr[k] = arr[k+1]
arr[k+1] = little
}
}
}
console.log(arr);
};
//当想要使用时:
sort( [1,2,3,4,5] );
//也可以这样:
var arr_a = [1,4,3,5,8];
sort(arr_a);
7.return
返回值
-
一个函数内处理的结果可以使用
return
返回,这样在调用函数的地方就可以用变量接收返回结果 -
按值返回
-
return
关键字内任何类型的变量数据或表达式都可以进行返回,甚至什么都不返回也可以。所以有的时候return
的作用就是用来终止函数执行。 -
JAVASCRIPT在事件中调用函数时用
return
返回值实际上是对window.event.returnvalue
进行设置,而该值决定了当前操作是否继续。
当返回的是true
时,将继续操作。
当返回是false
时,将中断操作。而直接执行时(不用return),将不会对
window.event.returnvalue
进行设置,所以会默认地继续执行操作
//一个加法运算
function add(){
var result = 0;
for( var i = 0,length = arguments.length;i < length;i++){
result += arguments[i];
}
return result;//将得到的result结果<值>返回出去
}
var some = add(1,2,3,4);
console.log( some );//10
8.作用域
📌当某个函数被调用时,会创建一个执行环境(作用域)及相应的作用域链。
📌作用域链规定我们在什么地方可以访问什么变量。作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
📌无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁(内存释放),内存中仅保存全局作用域(全局执行环境的变量对象)。
❓为什么会内存释放
局部变量只在函数执行的时候使用,执行完该函数后,功能已经实现了,没有存在的必要了。假如还存在的话,其他也没有人可以再使用它,那就是浪费内存,因此
js
的垃圾收集器会把该局部变量当做垃圾回收掉
es5
中只有函数作用域和全局作用域,es5
只要不在函数里边去声明一个变量,这变量就是全局(window,Global)- 全局作用域不可以使用局部作用域里的变量,局部作用域可以访问全局里的变量和所有父作用域。
- 对于全局和局部中名字一样的变量,采用就近原则。
- 变量提升:使用var声明的变量会进行变量提升,提升到当前作用域的顶层
var a = 1;//全局,这里的a内存中是存在的
function functionName(){//这个花括号就是函数的作用域
//在创建 functionName()函数时,会创建一个预先包含全局变量对象的作用域链
var a = 2;//局部,这里的a只在函数执行的过程中存在
}
9.闭包
(1)可以这样理解闭包:
- 闭包是指有权访问另一个函数作用域中的变量的函数。
- 闭包让函数在执行完成之后不释放内存;
- 函数执行完成之后阻止作用域被销毁。
(2)闭包形成的方式:
- 函数a返回(return)函数b,函数b在函数a之外执行,并且会用函数a里边的变量
- 在全局中声明一个变量保存这个声明的函数。(必须有人知道这个函数)
(3)例子:
//🎈第一个:返回函数并声明变量保存
function a() {
var num = 123;
return function b() {//函数a返回了函数b
console.log( num );//在函数b里边使用了函数a里的num变量
}
};
var result = a();//声明一个result去保存a()
result();//调用result()后,控制台可打印出123
//如果直接使用a(),控制台并不会打印出num的值
//📝也可以这样写:
function a() {
var num = 123;
function b() {
return num;
}
return b();//返回的是这个函数结果
//return b;//返回的是个函数
}
var result = a();
cobsole.log(result);//将返回的函数值123打印出来
//result();//调用的前提是上边返回的是个函数
//🎈第二个:返回的不是函数时
function c() {
var str = 'hello world';
return {
some : str
};
};
var result_b = c();
//result_b();//报错:Uncaught TypeError: result_b is not a function
console.log( result_b.some );//hello world
//🎈第三个:形成短暂闭包情况
function d() {
var arr = [1,2,3];
return function e(){
console.log(arr);
}
}
var res = d();
d()();//此时会短暂的形成闭包(两次调用)
(4)闭包与变量
闭包只能取得包含函数中任何变量的最后一个值。
闭包所保存的是整个变量对象,而不是某个特殊的变量
function createFunctions(){
var result = new Array();
for (var i = 0,i < 10; i++){
result[i] = function(){
return i;
}
}
return result;
}
这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返回自己的索引值,即位置 0 的函数返回 0,位置 1 的函数返回 1,以此类推。但实际上,每个函数都返回 10。因为每个函数的作用域链中都保存着 createFunctions()
函数的活动对象,所以它们引用的都是同一个变量 i
。 当createFunctions()
函数返回后,变量 i
的值是 10,此时每个函数都引用着保存变量i
的同一个变量对象,所以在每个函数内部i
的值都是 10。
这时,我们可以利用立即执行函数来解决这个问题
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
在这个版本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这里的匿名函数有一个参数 num
,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量 i
。由于函数参数是按值传递的,所以就会将变量 i
的当前值复制给参数 num
。而在这个匿名函数内部,又创建并返回了一个访问 num
的闭包。这样一来,result
数组中的每个函数都有自己num
变量的一个副本,因此就可以返回各自不同的数值了。
**练习:**点击盒子,控制台打印出保存该盒子的数组下标
<body>
<div class="box">0</div>
<div class="box">1</div>
<div class="box">2</div>
<div class="box">3</div>
</body>
<script>
var boxs = document.querySelectorAll('.box');
for( var i = 0; i < boxs.length; i++ ){
(function (a){
boxs[a].onclick = function(){
console.log(a);
};
})(i);
};
</script>
(5)闭包面试题
function fun(n, o){
console.log(o);
return {
fn: function (m) {
return fun(m, n);
}
};
};
看以上代码,执行完下列代码的每一行后,判断控制台打印的o的值
//第一题:
var a = fun(1);
a.fn(2);
a.fn(3);
a.fn(4);
//第二题:
fun(1).fn(2).fn(3).fn(4);
答案是:
//第一题:
var a = fun(1);
a.fn(2);//undefined 1
a.fn(3);//undefined 1
a.fn(4);//undefined 1
/*理由:
声明的变量a保存fun()时,形成闭包;
a.fn(2);短暂的形成闭包,执行完成后,该执行环境销毁
a.fn(3)和a.fn(4)同理
*/
//第二题:
fun(1).fn(2).fn(3).fn(4);//undefined 1 2 3
//理由:这个时候闭包一直都存在,之前保存的变量值未被销毁
10.this
(1)指向
this的默认指向window,还可以是调用函数的主体对象;(看谁调用了他)
- 在全局执行环境中使用this,指向window
console.log(this); //Window
console.log(typeof this); //object
console.log(this === window); //true
- 在函数中使用this,看是谁调用了他
function fn_a() {
function some() {
console.log(this);//这里的this依然是window
};
some();
}
fn_a();
在对象方法中绑定:
var obj = { //声明一个obj对象来保存下边的值
nickname: '小明',
sayName: function () {
console.log(this);//打印出整个obj对象里的内容
console.log('hello 我是:', this.nickname);//hello 我是: 小明
}
};
obj.sayName();// obj.sayName调用了上边这个函数
- 严格模式下单独使用this,指向window
"use strict";
var x = this;
- 严格模式下,函数中使用this,this 是未定义的(undefined)。
"use strict";
function myFunction() {
return this;
}
实例1:(主要为理解this指向)
var nickname = '小刚';
var a = {
nickname: '小烈',
b: {
nickname: '小红',
c: function() {
console.log('hello 我是:', this.nickname);
}
}
};
a.b.c();
//结果打印出 hello 我是: 小红
实例2:(主要使用this节省内存)
function some() {
console.log(this);
console.log( this.nickname );
};
some();
var aLI = {
nickname: '公孙离',
introduce: some
};
aLi.introduce();
var LiBai ={
nickname: '李白';
introduce: some
};
LiBai.introduce();
(2)切换this绑定的对象
由于js
中this
的指向受函数运行环境的影响,经常发生改变,有时会使开发变得困难,因此为避免出现一些不必要的问题,出现以下三种方法:
call ()
🎈改变this指向:call
的第一个实参是this指向。
var obj = {};
var f = function(){
return this;
};
console.log(f() === window); // this 指向window
console.log(f.call(obj) === obj) //改变this 指向 obj
🎈用call
可以去绑定任意的数据类型,如果是undefined
和null
则等于绑定了window。
function foo(a) {
console.log(a);//我是实参
console.log(this);//{nickname: '小明'}
};
foo.call( {nickname: '小明'}, '我是实参' );
var obj_b = {
b: {
c: function () {
console.log(this)
}
}
}
obj_b.b.c.call(undefined);
🎈call
可以接受多个参数,第一个参数是this指向的对象,之后的是函数回调的所需的入参。
function add(a, b) {
return a + b;
}
add.call(this, 1, 2) // 3
🎈所传的参数一次性使用,立即执行,不会产生新内容(不会改变原函数)
function foo(a) {
console.log(a);//我是实参
console.log(this);//{nickname: '小明'}
};
foo.call( {nickname: '小明'}, '我是实参' );
foo();//undefined window
📝应用:调用对象的原生方法
var obj = {};
obj.hasOwnProperty('toString') // false
// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
return true;
};
obj.hasOwnProperty('toString') // true
Object.prototype.hasOwnProperty.call(obj, 'toString') // false
apply()
🎈改变this指向:apply
第一个实参是this指向。
function some_a(a, b){
console.log(this); //{ nickname: '小明' }
};
some_a.apply( { nickname: '小明' }, [ '123', '456' ] );
🎈只接受两个参数,第一个是this指向,第二个必须是数组类型,数组每一项一一对应函数形参
function some_a(a, b){//a对应数组的第0项,b对应数组的第1项
console.log(a, b);//123 456
};
some_a.apply( { nickname: '小明' }, [ '123', '456' ] );
🎈所传的参数一次性使用,立即执行,不会产生新内容(不会改变原函数)
function some_a(a, b){//a对应数组的第0项,b对应数组的第1项
console.log(a, b);//123 456
};
some_a.apply( { nickname: '小明' }, [ '123', '456' ] );
some_a();//undefined undefined
📝应用:遍历数组
//一个加法运算
var arr = [1,2,3];
function add(a, b,c){
console.log( a + b + c );//6
};
add.apply( null, arr );
//输出数组的最大值:
var a = [24,30,2,33,1]
Math.max.apply(null,a) //33
bind()
bind 用于将函数体内的this绑定到某个对象,然后返回一个新函数。并不会立即执行
var obj = {
nickname: '小明'
};
function abs(a,b) {
console.log(this);//{nickname: '小明'}
console.log(a,b)//1 2
};
var result = abs.bind(obj);
result(1,2);
**实例:**点击每个div,将每个div中的字体变成不同的颜色
<style>
*{
margin: 0;
padding: 0;
list-style: none;
}
.box{
width: 100px;
height: 100px;
background-color: orange;
}
.box + .box{
margin-top: 10px;
}
</style>
<body>
<div class="box">0</div>
<div class="box">1</div>
<div class="box">2</div>
<div class="box">3</div>
</body>
<script>
var boxs = document.querySelectorAll('.box');
function a(e, color) {//字体颜色
// console.log(color)
console.log( this );
this.style.color = color;
};
//给div中字体赋予不同的颜色:
var colorful = ['#58a', 'purple', 'hotpink', 'yellowgreen'];
for( var i = 0; i < colorful.length; i++ ){
boxs[i].onclick = a.bind( boxs[i], 123, colorful[i] );
};
</script>
小结:
call
和apply
:相同点都是临时借用一个函数,并替换this为指定对象,不同点在于传参方式不一样,并且都是立即执行;bind
:基于现有函数,创建一个新函数,并且永久绑定this为其指定对象,可绑定参数,不过只是创建函数,不立刻执行。