函数

函数

函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。

函数是第一等公民

函数是本身也是一种值,类似字符串、数值、布尔。

函数的声明
  • 语句式声明
//函数语句式声明
function add( a, b ){
  return a+b;
}

  • 表达式声明
//函数表达式声明
var add = function( a, b ){
  return a+b;
}

  • 构造函数声明
var add = new Function( 'a', 'b','return a + b' );

示例

//语句式声明
function add( a, b ){
  return a+b;
}
console.log( add(1,5) );//6

//表达式声明
var add = function( a, b ){
  return a+b;
}
console.log( add(1,5) );//6

//构造函数声明
var add = new Function( 'a', 'b','return a + b' );
console.log( add(1,5) );//6

语句式声明和表达式声明有什么区别?

  • 语句式声明会自动提升
//函数会自动提升,不会报错
console.log( add(1,5) ); //6

//语句式声明
function add( a, b ){
  return a+b;
}

console.log( add(1,5) ); //6

  • 表达式声明不提升
//不会提升,致命错误
console.log( add(1,5) ); //Uncaught TypeError: add is not a function

//表达式声明
var add = function( a, b ){
  return a+b;
}
console.log( add(1,5) ); //6

构造函数声明可以用来解析字符串表达式,类似eval

console.log( eval('5+6*2') );//17

var fn = new Function( 'return 5+6*2' );
console.log( fn() );//17

函数的重复声明

代码是从上到下执行,后面的函数覆盖前面的函数,注意语句式声明函数会提升到代码顶部

//var 定义 会覆盖
var a = '111';
var a = '222';
console.log( a ); //会覆盖 222

//let 定义 不会覆盖
let b = 'aaa';
let b = 'bbb';

console.log( b );//报错,重定义错误

//const 定义 不会覆盖
const c = 'true';
const c = 'false';//报错,重定义错误

//函数 重定义 会覆盖
function add(){
  console.log('hello');
}

var add = function(){
  console.log('您好');
}

function add(){
  console.log('fine');
}

add(); //因为语句式声明会提升,表达式不提升,所以输出您好

圆括号运算符,return 语句和递归

  • 圆括号

调用函数时,要使用圆括号运算符。圆括号之中,可以加入函数的参数。

function add(x, y) {
  return x + y;
}

//调用函数加圆括号,在括号之中加入参数
add(1, 1) // 2

  • return 语句

return返回并且跳出函数,return是函数内部的语句。

returnbreak的区别

function echo(){
  for(var i=0;i<10;i++){
    if(i>5){
      return i; //跳出函数
      break; //跳出循环
    }
  }
  return 'hello';
}

console.log( echo() ); //6

return 是函数内部语句,必须有函数包裹

for( var i=0; i<10; i++ ){
  console.log( i );
  if(i>5){
    return;
  }
}
//报错,Uncaught SyntaxError: Illegal return statement

  • 函数递归

函数可以调用自身,这就是递归,递归一定要有个结束值(return),不然会形成死循环。

//因为没有结束条件,所以这是一个死循环
function fn( n ){
  console.log( n );
  fn( n+1 );
}
fn( 0 );//0 1 2 3 .....

//降序
function desc( n ){
  console.log( n );
  if( n == 0 ) return;
  desc( n-1 );
}

desc( 5 );//5 4 3 2 1 0

//升序
function asc( n ){
  console.log( n );
  if( n == 5 ) return;
  asc( n + 1 );
}

asc( 0 );//0 1 2 3 4 5


//阶乘
function fn( n ){
  // console.log( n );
  if( n == 0 ) {
    return 1;
  }else{
    return n * fn( n-1 );
  }
}

//斐波那契数列
function fib(num) {
  if (num === 0) return 0;
  if (num === 1) return 1;
  return fib(num - 2) + fib(num - 1);
}

fib(6) // 8

函数是第一等公民

JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。

  • 可以把函数赋值给变量
var add = function( a, b){
  return a+b;
}

  • 可以把函数赋值给对象的属性
var obj = {
  name: '张三',
  getAge: function(){
    return 30;
  }
}

  • 可以当作参数传入其他函数(作为参数时不能加括号)
//1. 将函数作为参数,并提取出来声明
function add( a, b ){
  console.log( a ); // function(){ }
  console.log( b ); // 5
  console.log( a() + b );//要加括号 需要执行
}

function fn(){
  return 10;
}

add( fn, 5 );//15

//2. 将函数作为参数声明
function add( a, b ){
  console.log( a() + b );
}

add( function(){
  return 10;
}, 5 );// 15


函数作为参数传入到函数内部,我们也叫这个函数(参数)回调函数

//仿照数组实例forEach
function forEach( arr, fn ){
  for(var i=0;i < arr.length;i++){
    fn( arr[i], i, arr );
  }
}

//调用
var arr = ['a','b','c','d'];
forEach( arr, function( item, index, arr){
  console.log( item, index, arr );
});

//仿照数组实例fliter
function filter( arr, fn ){
  var newArr = [];
  for(var i=0; i < arr.length; i++ ){
    var result = fn( arr[i], i, arr );
    if( result ){
      newArr.push( arr[i] );
    }
  }
  return newArr;
}
var newData = filter( data, function( item, index, arr ){
  return item.name=='张三';
});
console.log( newData );

函数名的提升

JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错。

f();
function f() {}

变量提升

//1
console.log( a ); // 报错,未定义


//2
console.log( a ); // undefined
var a;

//3
console.log( a ); // undefined
var a = 10;

//4
console.log( a ); // undefined
var a = 10;
console.log( a ); // 10


变量提升面试题

function foo( x ){
  if( x > 100 ) {
    var tmp = x - 100;
  }
  console.log( tmp ); 
}

foo( 50 ); // undefined

//以上代码变量会提升,等同于
function foo( x ){
  var tmp;
  if( x > 100 ) {
    tmp = x - 100;
  }
  console.log( tmp ); 
}

不能在条件语句中声明函数

不要在条件语句是声明函数,以下写法不推荐

var foo = false;
if ( foo ) {
  function fn() {
    console.log('hello');
  }
}

fn();//报错 fn is not a function

函数的属性和方法

name 属性

//语句式声明
function f1() {
}
f1.name // "f1"

//表达式声明
var f1 = function add(){
}

f1.name // "add"

递归函数解耦

//递归
function desc( n ){
  console.log( n );
  if(n==0)return;
  arguments.callee( n-1 ); // arguments.callee 存在兼容性问题
}
desc( 10 );

//替换上面的方法,兼容性更好
var desc = function fn( n ){
  console.log( n );
  if( n == 0 ) return;
  fn( n-1 );
}
desc( 5 );

length 属性

函数的长度指的是形参的个数

function fn( a, b, c, d ){
  console.log( fn.length );
}
console.log( fn.length ); // 4
fn(); // 4

toString() 函数的toString方法返回一个字符串,内容是函数的源码。

function add( a, b ){
  /*
  这是一个
  多行注释
  */
  return a+b;
}
console.log( add.toString() );

函数作用域

作用域(scope)指的是变量存在的范围

js两种作用域:

  • 全局作用域(全局变量):

变量在整个程序中一直存在,所有地方都可以读取,关闭当前窗口,才能释放内存。

<script>
var str = '张三';//全局变量
</script>

  • 函数作用域(局部变量):

变量只在函数内部存在

注意:在函数内部使用var关键字声明的变量是局部变量

function fn(){
  var str = '李四';
}
fn();
console.log( str ); //报错 str is not defined

注意:在函数内部省略var关键字,会自动提升为全局变量。

function fn(){
  str = '李四';
}
fn();
console.log( str ); // 李四

<script>
var str = '张三';//全局变量
function fn(){
  var age = 20; //局部变量
}
</script>

变量提升面试题 1

fn(); // undefined 20

var str = '张三'; 
function fn(){
  var age = 20;
  console.log( str, age);
}


变量提升面试题 2

function fn(){
  if ( false ) {
    var x = 5;
  }
  console.log(x); 
}

fn(); // undefined

变量提升面试题 3

//代码从上到下执行
function x(){
  console.log(a);
  a = 10;
}
x();
console.log( a ); // Uncaught ReferenceError: a is not defined

变量提升面试题 4

function x(){
  console.log(a);
  var a = 10;
}
x(); // undefined
console.log( a ); // Uncaught ReferenceError: a is not defined

函数作用域面试题 1

var str = '张三'; 
function fn(){
  var age = 20;
  var str = '李四';
  console.log( str, age);
}

fn(); // 李四 20

console.log( str ); // 张三

函数作用域面试题 2

var str = '张三'; 
function fn(){
  var age = 20;
  str = '李四';
  console.log( str, age);
}

fn(); // 李四 20

console.log( str ); // 李四

函数作用域面试题 3

function one( ){

  var str = '张三';
  function two(){
    var age = 20;
    console.log( str );
  }
  two();

  console.log( age );

}

one(); // 报错 

函数本身的作用域

函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时(调用时)所在的作用域无关

情况 1

var a = 1;
//声明时作用域
var x = function(){
  console.log(a);
}

function f() {
  var a = 2;
  //运行时(调用时)作用域
  x();
}

f();//1

情况 2

var a = 1;
//声明时作用域
var x = function(){
  var a = 5;
  console.log(a);
}

function f() {
  var a = 2;
  //运行时(调用时)作用域
  x();
}

f();//5

情况 3

//声明时作用域
var x = function(){
  console.log(a);
}

function f() {
  var a = 2;
  //运行时(调用时)作用域
  x();
}

f(); // a is not defined

情况 4

//声明时作用域
var x = function(){
  console.log(a);
  a = 10;
  //注意没有使用var关键字 则没有优先提升权限
}

function f() {
  var a = 2;
  //运行时(调用时)作用域
  x();
}

f(); // Uncaught ReferenceError: a is not defined

函数闭包

在函数内部返回函数,返回的函数调用了父函数的局部变量,这样形成了闭包。

  • 闭包可以将函数内部的局部变量暴露给外部,是函数内部和外部的桥梁
  • 闭包会导致函数内部的局部变量无法释放,不被垃圾回收站回收。

闭包的缺点:闭包因为会使局部变量内存无法及时释放,如果使用太多会增加内存开销,带来性能隐患。

function one(){
  var name = '张三';
  function two(){
    console.log( name );
  }
  return two;
}

var fn = one();
fn();

闭包经典面试题 1

function bb(){
  var num = 999;
  return function(){
    num++;
    console.log( num );
  }
}

var fun = bb();// function(){}
fun(); // 1000
fun(); // 1001

参数

函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。

function square(x) {
  return x * x;
}

参数可以省略,从后向前省略,形参和实际参数一一对应。

运行时无论提供多少个参数,或者不提供参数都不会报错,省略的参数值是undefined

函数的length只和形式参数个数有关,和实际参数无关

没有办法只省略靠前的参数,而保留靠后的参数,从后向前省略。

形参的作用域是函数作用域,也就是局部变量

//1 使用条件语句
function add( a, b, c){
  if(a==undefined) a =0;
  if(b==undefined) b =0;
  if(c==undefined) c =0;
  return a + b + c;
}

console.log(  add( 1, 2 ) );

//2 使得短路法则
function add( a, b, c){
  a = a || 0;
  b = b || 0;
  c = c || 0;
  return a + b + c;
}

console.log(  add( 1, 2 ) );

//3 默认参数 es6
function add( a = 0, b = 0, c = 0){
  return a + b + c;
}
console.log(  add( 1, 2 ) );

函数参数传递方式
  • 参数为原始类型(数值、字符串、布尔、undefined、null)传递方式是传值传递。
  • 参数为引用类型(对象、数组)传递方式是传址传递。

比较下面两道题,注意区分:

//函数内部没有加var关键字,认为全局变量
var p = 2;
function f() {
  p = 3;
}
f();
console.log( p );//3

//因为p是形参,而它是直接拷贝 2,内部覆盖它的值为3 不会影响到全局变量p
var p = 2;
function f( p ) {
  p = 3;
}
f( p );
console.log( p );//2

数组是传址传递,所以在数组内部会影响到外面传入的数组

var arr = ['苹果','桔子'];
function f( arr ) {
  arr[1] = '香蕉';
}
f( arr );
console.log( arr );//['苹果','香蕉']

解决办法,就是复制一个新数组

//使用es6扩展运算符生成新数组
var arr = ['苹果','桔子'];
function f( [...arr] ) {
  arr[1] = '香蕉';
}
f( arr );
console.log( arr );//['苹果','桔子']

//使用concat生成新数组
var arr = ['苹果','桔子'];
function f( arr ) {
  arr = [].concat( arr );
  arr[1] = '香蕉';
}
f( arr );
console.log( arr );//['苹果','桔子']


对象因为也是引用类型,所有在函数内部修改对象时会改变外面的对象

var obj = {
  name: '张三',
  info: {
    age: 20
  }
};

function f( obj ) {
  obj.info.age = 30;
}
f( obj );
console.log( obj );//年龄会被该成30


同名参数

如果有同名的参数,则取最后出现的那个值。

即使后面的a没有值或被省略,也是以其为准。

function f(a, a) {
  console.log(a);
}

f(1, 2) // 2

arguments 参数集合对象

由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来。

arguments 是一个类数组对像,它包含了数组所有参数。

arguments 对象包含了函数运行时的所有参数(实际参数)

arguments.callee 指向的是函数本身

function max(){
  var args = arguments;
  var arr = [];

  if( args.length > 1 ){
    arr = Array.from( args );
  }else{
    if( typeof args[0] =='string' ){
      arr = args[0].split('');
    }else{
      arr = args[0];
    }
  }

  arr = arr.filter(function(item){
    return isNaN(item) == false;
  })

  var max = arr[0];
  for(var i=0;i<arr.length;i++){
    if ( max < arr[i] ) max = arr[i];
  }
  console.log( max );

}

max('230a1'); 
max(2,'a',30,1);
max([2,'a',13,1]);

经典面试题:多种办法单击获取索引值

<ul>
    <li>首页</li>
    <li>关于我们</li>
    <li>产品中心</li>
</ul>
<script>
var lis = document.querySelectorAll('li');

//1. es6 块级作用域实现
for(let i = 0; i < lis.length; i++ ){
  lis[i].onclick = function(){
    console.log( i );
  }
}

//2.将索引值缓存到对象上,然后再到对象自身去取
for(var i = 0; i < lis.length; i++ ){
  lis[i].index = i;
  lis[i].onclick = function(){
    console.log( this.index );
  }
}

//3. 使用闭包来缓存索引值
for(var i = 0; i < lis.length; i++ ){
  function fn( j ){
    lis[j].onclick = function(){
      console.log( j );
    }
  }
  fn( i );
}

//4. 使用数组的indexOf方法获取索引值
var arr = Array.from( lis );
for(var i = 0; i < lis.length; i++ ){
  lis[i].onclick = function(){
    console.log( arr.indexOf( this ) );
  }
}
</script>

立即调用的函数表达式 IIFE

圆括号()是一种运算符,跟在函数名之后,表示调用该函数。

比如,print()就表示调用print函数。

!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();

//官方建议
( function(){ /* code */ } )();
( function(){ /* code */ }() );

示例

+function( a, b ){
  console.log(a+b);
}(2,3);

(function( a, b ){
  console.log(a+b);
})(2,3);

(function( a, b ){
  console.log(a+b);
}(2,3));

立即执行函数用途:

  • 不必为函数命名,避免了污染全局变量
  • IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量

用js写原生插件或者组件,我们习惯用IIFE封装它,避免污染全局变量。

(function(){

  var iAlert = function(){
    console.log('我是警告框插件!');
  }

  //将私有的变量注册为全局变量
  window.iAlert = iAlert;

}());

//调用插件
iAlert();

定义组件时,组件默认参数通常采用覆盖替换法实现

//默认参数
var oldObj = {
  page: 1,
  perpage: 5
}

//用户参数
var newObj = {
  page: 3,
  perpage: 10
}

//合并默认参数和用户参数
var obj = Object.assign( {}, oldObj, newObj);
// var obj = { ...oldObj, ...newObj };
console.log( obj );

js 严格模式

默认是比较宽松的模式

如何设置严格模式?

全局设置

<script>
//设置严格模式
'use strict'

</script>

函数内设置

<script>
//函数内设置严格模式
function fn(){
  'use strict'

}
</script>

严格模式有那些要求(部分)?

  1. 声明变量需要使用关键字varletconst,否则报错
'use strict'
str = '张三';
console.log(str); //报错

  1. 不允许函数的形参重名
'use strict'
function x(p1, p1) {
} //报错

  1. 不允许删除一个不允许删除的属性
'use strict';
delete Object.prototype; //错误

  1. 变量名不能使用 arguments 字符串
'use strict';
var arguments = 10;
console.log( arguments );

  1. 由于一些安全原因,在作用域 eval() 创建的变量不能被调用
'use strict';
eval ('var x = 2');
alert (x); 

  1. 禁止this关键字指向全局对象。
'use strict';
function fn(){
  console.log(this);
}

fn();//本来是指向windows,但在严格模式下不允许,返回undefined

  1. 不允许删除变量或对象。
'use strict';
var x = 3.14;
delete x; 

使用new Function 将字符串当作命令执行。

语法

new Function('参数1','参数2','...','函数内部返回');

最后一个参数始终是函数内部返回代码

var jsonp = 'foo({"id": 42})';

// foo是函数的参数,它是一个函数(回调函数)。
var f = new Function( 'foo', jsonp );

// 相当于定义了如下函数 
// function f(foo) {
//   foo({"id":42}); //回调函数
// }

//调用 
f(function (json) {
  console.log( json.id ); // 42
})

jsonp原理

为了便于客户端使用数据(比如远程数据),逐渐形成了一种非正式传输协议,进行数据传送,这种方式我们叫JSONP跨域,该协议的一个要点就是允许服务端返回一个回调函数,这个回调函数包裹这数据,这样客户端就可以方便的取到服务器返回的数据。

jsonp主要原理是利用js回调函数机制实现一种非正式跨域。

  • 远程服务端返回一个函数,该函数包裹数据
//服务端
//假设以下 http://www.jx-tongan.com/vueapi/hello.js 的内容
fn({
  name:'张三',
  age: 20,
  habby: ['钓鱼','游泳']
});

  • 客户端通过回调函数的形参获取到服务端返回的数据
<script>
//客户端
//服务端的数据在形参obj上 
function city( obj ){
  console.log( obj );
}  
</script>

<script src="js/city.js"></script>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值