js之函数使用介绍

207 阅读6分钟

基础知识

函数是将复用的代码块封装起来的模块,在JS中函数还有其他语言所不具有的特性,接下来我们会详细掌握使用技巧。

声明定义

在JS中函数也是对象函数是Function类的创建的实例,下面的例子可以方便理解函数是对象。

    let fn = new Function('title', 'console.log(title)')
    fn('我是函数') // 输出 我是函数

标准语法是使用函数声明来定义函数

    function fn2(num) {
      console.log(++num);
    }
    fn2(3)  // 4

对象字面量属性函数简写

 let user = {
      uname: '张三',
      age: 18,
      hobby: [],
      setHobby(hobby) {
        this.hobby = hobby
      }
    }

    user.setHobby(['唱', '跳', 'rap'])
    console.log(user.hobby); // (3) ['唱', '跳', 'rap']

全局函数会声明在window对象中,这不正确建议使用后面章节的模块处理

function fn3() {
      console.log(33);
    }

  window.fn3(); // 33

当我们定义了 screenX 函数后就覆盖了window.screenX方法

function screenX() {
  return "测试";
}
console.log(screenX()); //测试

使用let/const时不会压入window

   let screenX = 1
    console.log(screenX); // 1
    console.log(window.screenX); // 0

匿名函数

函数是对象所以可以通过赋值来指向到函数对象的指针,当然指针也可以传递给其他变量,注意后面要以;结束。下面使用函数表达式将 匿名函数 赋值给变量

    // 匿名函数
    let fn1 = function (num) {
      console.log(++num);
    }
    // instanceof  检测对象的原型是否是在这个构造函数
    console.log(fn1 instanceof Object); // true

    fn1(3); // 4

标准声明的函数优先级更高,解析器会优先提取函数并放在代码树顶端,所以标准声明函数位置不限制,所以下面的代码可以正常执行。

console.log(hd(3));
function hd(num) {
    return ++num; // 4
};

标准声明优先级高于赋值声明

console.log(hd(3)); //4

function hd(num) {
  return ++num;
}

var hd = function() {
  return "hd";
};

程序中使用匿名函数的情况非常普遍

function sum(...args) {
  return args.reduce((a, b) => a + b); // 6
}
console.log(sum(1, 2, 3));

立即执行

立即执行函数指函数定义时立即执行

  • 可以用来定义私有作用域防止污染全局作用域
 // 使用严格模式
    'use strict';
    (function () {
      let age = 10
    })();
    console.log(age); // age is not defined

使用 let/const 有块作用域特性,所以使用以下方式也可以产生私有作用域

{
	let web = 'baidu.com';
}
console.log(web); // web is not defined

函数提升

函数也会提升到前面,优先级行于var变量提高

console.log(hd()); //zhangsan
function hd() {
    return 'zhangsan';
}

变量函数定义不会被提升

 fn1() // 我会提升
    fn2() // fn2 is not a function

    function fn1() {
      console.log('我会提升');
    };
    var fn2 = function () {
      console.log('我不会提升');
    };

默认参数

    function avg(total, year) {
      // 如果year有值就取year的值,没有就等于1
      year = year || 1
      return Math.round(total / year)
    }
    console.log(avg(2000, 3)); // 667

使用新版本默认参数方式如下

function avg(total, year = 1) {
  return Math.round(total / year);
}
console.log(avg(2000, 3));// 667

下面通过排序来体验新版默认参数的处理方式,下例中当不传递 type 参数时使用默认值 asc。

// 定义一个数组排序函数,默认升序
    function sort(arr, type = 'asc') {
      return arr.sort((a, b) => type === 'asc' ? (a - b) : (b - a))
    }
    console.log(sort([1, 32, 45, 7, 3, 554, 67])); // (7) [1, 3, 7, 32, 45, 67, 554]
    console.log(sort([4, 54, 6, 77, 556, 456, 22, 4], 'desc')); // (8) [556, 456, 77, 54, 22, 6, 4, 4]

函数可以做为参数传递

function filterFun(item) {
	return item <= 3;
}
let hd = [1, 2, 3, 4, 5].filter(filterFun);
console.log(hd); //[1,2,3]

arguments

arguments 是函数获得到所有参数集合,下面是使用 arguments 求和的例子

function sum() {
  return [...arguments].reduce((total, num) => {
    return (total += num);
  }, 0);
}
console.log(sum(2, 3, 4, 2, 6)); //17

更建议使用展示语法

function sum(...args) {
 return args.reduce((a, b) => a + b);
}
console.log(sum(2, 3, 4, 2, 6)); //17

箭头函数

箭头函数是函数声明的简写形式,在使用递归调用、构造函数、事件处理器时不建议使用箭头函数。 无参数时使用空扩号即可

let sum = () => {
	return 1 + 3;
}
console.log(sum()); //4

函数体为单一表达式时不需要 return 返回处理,系统会自动返回表达式计算结果

let sum = () => 1 + 3;
console.log(sum()); //4

多参数传递与普通声明函数一样使用逗号分隔

let hd = [1, 8, 3, 5].filter((item, index) => {
	return item <= 3;
});
console.log(hd); // 1,3

只有一个参数时可以省略括号

let hd = [1, 8, 3, 5].filter(item => item <= 3);
console.log(hd);// 1,3

this

调用函数时 this 会隐式传递给函数指函数调用时的关联对象,也称之为函数的上下文。

函数调用

全局环境下this就是window对象的引用

<script>
  console.log(this == window); //true
</script>

使用严格模式时在全局函数内thisundefined

   var age = 19
    function fn() {
      'use strict'
      console.log(this.age); // Cannot read properties of undefined (reading 'age')
    }
    fn();
//严格模式将产生错误 Cannot read property 'name' of undefined

方法调用

函数为对象的方法时this 指向该对象 可以使用多种方式创建对象,下面是使用构造函数创建对象

构造函数

函数当被 new 时即为构造函数,一般构造函数中包含属性与方法。函数中的上下文指向到实例对象。

  • 构造函数主要用来生成对象,里面的this默认就是指当前对象
function User() {
  this.name = "zhangsan";
  this.say = function() {
    console.log(this); //User {name: "zhangsan", say: ƒ}
    return this.name;
  };
}
let hd = new User();
console.log(hd.say()); //zhangsan

对象字面量

  • 下例中的hd函数不属于对象方法所以指向window
  • show属于对象方法执向 obj对象
let obj = {
  site: "zhangsan",
  show() {
    console.log(this.site); //zhangsan
    console.log(`this in show method: ${this}`); //this in show method: [object Object]
    function hd() {
      console.log(typeof this.site); //undefined
      console.log(`this in hd function: ${this}`); //this in hd function: [object Window]
    }
    hd();
  }
};
obj.show();

在方法中使用函数时有些函数可以改变this如forEach,当然也可以使用后面介绍的apply/call/bind

    let lessons = {
      site: '张三',
      lists: ["js", "css", "mysql"],
      show() {
        return this.lists.map(item => {
          return item = `${this.site}${item}`
        }, this)
      }
    }
    console.log(lessons.show()); // (3) ['张三的js', '张三的css', '张三的mysql']

也可以在父作用域中定义引用this的变量

    let lessons = {
      site: '张三',
      lists: ["js", "css", "mysql"],
      show() {
        const self = this
        return this.lists.map(item => {
          return item = `${self.site}${item}`
        })
      }
    }
    console.log(lessons.show()); // (3) ['张三的js', '张三的css', '张三的mysql']

箭头函数

箭头函数没有this, 也可以理解为箭头函数中的this 会继承定义函数时的上下文,可以理解为和外层函数指向同一个this。

  • 如果想使用函数定义时的上下文中的this,那就使用箭头函数 下例中的匿名函数的执行环境为全局所以 this 指向 window
  // 因为var声明的变量会放到全局window下
    var name = 'lisi'
    let obj = {
      name: 'zhangsan',
      getName: function () {
        return function () {
          return this.name
        }
      }
    }
    console.log(obj.getName()()); // lisi

以往解决办法会匿名函数调用处理定义变量,然后在匿名函数中使用。

var name = 'lisi';
let obj = {
  name: 'zhangsan',
  getName: function () {
    var self = this;
	return () => {
            return self.name;
    }
  }
}
console.log(obj.getName()()); //zhangsan

使用箭头函数后 this 为定义该函数的上下文,也可以理解为定义时父作用域中的this

var name = 'zhangsan';
var obj = {
  name: 'lisi',
  getName: function () {
    return () => {
    	return this.name;
    }
  }
}
console.log(obj.getName()()); //lisi

apply/call/bind

改变this指针,也可以理解为对象借用方法,就现像生活中向邻居借东西一样的事情

原理分析

构造函数中的this默认是一个空对象,然后构造函数处理后把这个空对象变得有值。

    function User(name) {
      this.name = name
    }
    let zs = new User('zhangsan')
    console.log(zs); // User {name: 'zhangsan'}

可以改变构造函数中的空对象,即让构造函数this指向到另一个对象。

    function User(name) {
      this.name = name
    }
    let obj = {}
    // 将构造函数this改成obj空对象
    User.call(obj, 'lisi')
    console.log(obj); // {name: 'lisi'}

apply/call

call与apply 用于显示的设置函数的上下文,两个方法作用一样都是将对象绑定到this,只是在传递参数上有所不同。

  • apply 用数组传参
  • call 需要分别传参
  • 与 bind 不同 call/apply 会立即执行函数
    function show(title) {
      console.log(`${title}-${this.name}`);
    }
    const zhangsan = {
      name: 'zhangsan'
    }
    const lisi = {
      name: 'lisi'
    }

    show.call(zhangsan, '法外狂徒') // 法外狂徒-zhangsan

    show.apply(lisi, ['法外狂徒'])  // 法外狂徒-lisi

使用 call 设置函数上下文

<body>
  <button message="我是button1">button</button>
  <button message="我是button2">button</button>
  <script>


    function show() {
      alert(this.getAttribute('message'));
    }
    let bts = document.getElementsByTagName('button');
    for (let i = 0; i < bts.length; i++) {
      bts[i].addEventListener('click', () => show.call(bts[i]));
    }
   </script>

找数组中的数值最大值

let arr = [1, 3, 2, 8];
console.log(Math.max(arr)); //NaN
console.log(Math.max.apply(Math, arr)); //8
 console.log(Math.max(...arr)); //8

bind

bind()是将函数绑定到某个对象,比如 a.bind(hd) 可以理解为将a函数绑定到hd对象上即 hd.a()。

  • 与 call/apply 不同bind不会立即执行
  • bind 是复制函数形为会返回新函数

bind是复制函数行为

let a = function() {};
let b = a;
console.log(a === b); //true
//bind是新复制函数
let c = a.bind();
console.log(a == c); //false

绑定参数注意事项

function hd(a, b) {
  return this.f + a + b;
}

//使用bind会生成新函数
let newFunc = hd.bind({ f: 1 }, 3);

//1+3+2 参数2赋值给b即 a=3,b=2
console.log(newFunc(2));

动态改变元素背景颜色,当然下面的例子也可以使用箭头函数处理

<style>
  * {
    padding: 0;
    margin: 0;
  }

  body {
    width: 100vw;
    height: 100vh;
    font-size: 3em;
    transition: 2s;
    display: flex;
    justify-content: center;
    align-items: center;
    background: #34495e;
    color: #34495e;
  }
</style>

<body>
  五颜六色
</body>
<script>
  function Color(elem) {
    this.elem = elem;
    this.colors = ["#74b9ff", "#ffeaa7", "#fab1a0", "#fd79a8"];
    this.run = function () {
      setInterval(
        function () {
          let pos = Math.floor(Math.random() * this.colors.length);
          this.elem.style.background = this.colors[pos];
        }.bind(this),
        1000
      );
    };
  }
  let obj = new Color(document.body);
  obj.run();
</script>

image.png