javascript 函数【知识点整理】

JavaScript(简称JS)是一种广泛使用的脚本语言,主要用于网页开发,但它也被用于各种非浏览器环境。在JavaScript中,函数是一种特殊的对象,用于封装可重复使用的代码块。以下是JavaScript中函数的基本格式和创建方式:

函数的基本格式

JavaScript函数的基本格式如下:

function functionName(parameters) {
    // 代码块
}
  • function 是创建函数的关键字。
  • functionName 是函数的名称,它应该遵循标识符的命名规则。
  • parameters 是函数的参数列表,多个参数之间用逗号分隔。在函数体内部,这些参数可以像变量一样使用。
  • 函数体(// 代码块)是实际执行的代码。

函数的创建方式

  1. 函数声明(Function Declaration)

这是最传统的方式,使用function关键字。

function greet(name) {
    console.log('Hello, ' + name + '!');
}

函数声明会提升(hoisting),这意味着函数可以在声明之前被调用。

  1. 函数表达式(Function Expression)

函数也可以作为表达式被创建,通常赋值给一个变量。

var greet = function(name) {  //匿名函数
    console.log('Hello, ' + name + '!');
};

greet('Alice'); // 输出: Hello, Alice!

函数表达式不会提升,必须在调用之前定义。

  1. 箭头函数(Arrow Function)

ES6(ECMAScript 2015)引入了箭头函数,这是一种更简洁的函数写法。

const greet = (name) => {
    console.log('Hello, ' + name + '!');
};
greet('Alice'); // 输出: Hello, Alice!

const greet = name => console.log('Hello, ' + name + '!'); //精简用法
greet('Bob'); // 输出: Hello, Bob!

如果函数体只有一条语句,可以省略花括号,并且隐式返回该语句的结果。

const greet = name => 'Hello, ' + name + '!';

箭头函数没有自己的thisargumentssupernew.target,这些值都由外围最近一层非箭头函数决定。

  1. 方法(Method)

在对象内部定义的函数,称为方法。

const person = {
    name: 'John',
    greet: function() {
        console.log('Hello, ' + this.name + '!');
    }
};
  1. 构造函数

使用new关键字创建的函数,用于创建对象实例。

function Person(name) {
    this.name = name;
}

const person = new Person('John');
  1. Function 构造函数

使用Function构造函数动态创建函数。

const greet = new Function('name', 'console.log("Hello, " + name + "!");');

这种方法不常用,因为它可能导致安全问题,并且不利于JavaScript引擎优化。

函数参数传递

在JavaScript中,函数参数的传递方式主要有两种:按值传递和按引用传递。

  1. 按值传递(Pass by Value)
  • 基本数据类型(如 NumberStringBooleannullundefinedSymbol)是通过值传递的。
  • 当你将一个基本数据类型的值作为参数传递给函数时,实际上是传递了这个值的一个副本
  • 函数内部对这个参数的任何修改都不会影响到原始数据
function modifyValue(value) {
    value = 10;
}

let a = 5;
modifyValue(a);
console.log(a); // 输出 5,因为函数内部修改的是值的副本,不影响原始变量
  1. 按引用传递(Pass by Reference)
  • 复杂数据类型(如 ObjectArrayFunction)是通过引用传递的。
  • 当你将一个对象作为参数传递给函数时,实际上是传递了这个对象**引用的副本,**但这个副本指向的是同一个内存地址。
  • 函数内部对这个参数对象的属性进行修改,会影响到原始对象。
function modifyObject(obj) {
    obj.name = 'Kimi';
}

let person = { name: 'Alice' };
modifyObject(person);
console.log(person.name); // 输出 'Kimi',因为函数内部修改的是对象本身

需要注意的是,虽然对象是按引用传递的,但是传递给函数的是引用的副本,这意味着你不能改变传递的对象引用本身(例如,将对象参数赋值为另一个新对象),只能修改对象的属性。

function changeObject(obj) {
    obj = { name: 'Bob' };
}

let person = { name: 'Alice' };
changeObject(person);
console.log(person.name); // 输出 'Alice',因为函数内部修改的是引用的副本,不影响原始对象引用

在JavaScript中,没有真正的按引用传递,因为即使对于对象,传递给函数的也是引用的副本。这种传递方式有时被称为“按共享传递”或“按引用传递的副本”。

函数作用域

在JavaScript中,作用域的概念对于变量的访问和生命周期至关重要

全局作用域
在函数体外部定义的变量
在函数体内部定义的无var的变量 函数要先执行后输出
在任何位置都可以调用

// 全局变量
var globalVar = "global variable";

// 全局函数
function globalFunction() {
  console.log("global function");
}

// 在全局作用域中访问全局变量和函数
console.log(globalVar); // 输出 global 
globalFunction();       // 输出:I am a global function

// 在函数内部访问全局变量和函数
function accessGlobal() {
  console.log(globalVar); // 输出:global variable
  globalFunction();       // 输出:global function
}

accessGlobal()

局部作用域(函数作用域)
在函数内部使用var定义的
函数的参数
在函数内部可以调用

function myFunction() {
  // 函数内部的变量,只在函数作用域内可见
  var localVar = "local variable";

  // 函数内部的函数,只在函数作用域内可见
  function localFunction() {
    console.log("local function");
  }

  // 在函数内部访问局部变量和函数
  console.log(localVar); // 输出:local variable
  localFunction();       // 输出:local function
}

// 尝试在全局作用域中访问函数内的局部变量和函数
// 这将导致错误,因为它们不在全局作用域中
// console.log(localVar); // ReferenceError: localVar is not defined
// localFunction();       // ReferenceError: localFunction is not defined

myFunction()

优先级
局部变量高于同名全局变量
参数变量高于同名全局变量
局部变量高于同名参数变量

内层函数可以访问外层函数局部变量外层函数不可以访问内层函数局部变量

无块级作用域

ES5中没有块级作用域,只有函数作用域,通过 var 声明的变量是函数作用域,并不是块级作用域。ES6中才引入块级作用域。

没有块级作用域意味着变量不会因为它们被声明在某个代码块(如if语句、for循环等)内部而局限于那个代码块。这会导致一些常见的问题,比如变量覆盖变量提升

1、 变量提升(Hoisting): 在JavaScript中,变量和函数声明会被提升到它们所在作用域的顶部。这意味着即使在代码中在声明之前使用了变量,它仍然可以被访问,因为它实际上在代码执行前就已经被“移动”到了顶部。

console.log(myVar); // 输出:undefined
var myVar = 10;

2、 没有块级作用域: 由于JavaScript没有块级作用域,var声明的变量即使在if语句或for循环内部声明,也会被提升到包含它们的函数或全局作用域中。

if (true) {
  var x = 5;
}
console.log(x); // 输出:5

3、 变量覆盖: 如果没有块级作用域,变量可能会被意外覆盖。例如,在一个循环中,使用var声明的变量可能会被多次覆盖,导致最终的值与预期不符。

for (var i = 0; i < 5; i++) {
  i = 10;
}
console.log(i); // 输出:10,而不是5

4、 letconst的引入: 为了解决这些问题,ES6引入了letconst关键字,它们提供了块级作用域。使用letconst声明的变量不会被提升,它们只在声明它们的代码块内部可见。

let x = 10;
if (true) {
  let x = 5; // 这个x只在if块内部可见
}
console.log(x); // 输出:10

5、letconst的暂时性死区(Temporal Dead Zone, TDZ): 与var不同,letconst声明的变量在代码块的开始到声明的位置之间形成了一个“死区”,在这个区域内访问这些变量会导致ReferenceError

console.log(x); // ReferenceError: x is not defined
let x = 10;

6.const的不变性: 使用const声明的变量必须在声明时初始化,并且其值不能被重新赋值(对于基本数据类型)。

const x = 10;
x = 20; // TypeError: Assignment to constant variable.

了解这些概念对于编写更安全、更可预测的JavaScript代码非常重要。使用letconst可以帮助避免许多由于作用域引起的错误。

示例

console.log(myFunction()); // 输出:Hello World

function myFunction() {
  return "Hello World";
}

在这个例子中,尽管myFunction函数在调用它的console.log语句之后被声明,但由于提升,函数在调用时已经存在,所以可以正常工作。

函数表达式的“提升”

对于函数表达式,JavaScript解释器只会提升变量(包括函数名),但不会提升函数表达式的值(即函数体)。这意味着函数表达式必须在声明之后才能被调用。

示例

  console.log(myFunction()); // 输出:undefined

  var myFunction = function() {
    return "Hello World";
  };
}

在这个例子中,myFunction变量被提升到了作用域的顶部,但是它被初始化为undefined,因为函数表达式的值(函数体)没有被提升。因此,当尝试调用myFunction()时,它仍然是undefined,导致调用失败。

总结

  • 函数声明的“提升”:函数声明的整个内容(包括函数名和函数体)都被提升到作用域的顶部。
  • 函数表达式的“提升”:只有变量名被提升,函数表达式的值(函数体)没有被提升。

这就是为什么函数声明可以在声明之前被调用,而函数表达式则不能。这种差异是JavaScript作用域和提升机制的一个重要特点。

(function($){
            cat = {
                name:"mimi",
                age:"2"
            }
            $.ModuleB = cat;
        })(window);

THIS

在JavaScript中,this关键字是一个非常重要的概念,它指向函数执行的上下文环境。this的值取决于函数是如何被调用的,而不是在哪里定义的。

window.color = "blue";
        var obj = {
            color:"red",
            sayHello:function(){
                return this.color;
            }
        }
        console.log(this.color);   //输出:blue
        console.log(obj.sayHello());   //输出:red

谁调用某个属性或方法,this就指向谁。

function a(){
            var user = "xiaoming"
            console.log(this.user);  // 输出:undefined
            console.log(this)   // 输出:window{..}
        }
        a();
var o = {
            a:10,
            b:{
                a:12,
                fn:function(){
                    console.log(this.a)
                }
            }
        }
        o.b.fn();  // 输出:12
 var o = {
            a:10,
            b:{
                a:12,
                fn:function(){
                    console.log(this.a)  // 输出:undefined
                    console.log(this)   // 输出:Window{..}
                }
            }
        }
        var j =  o.b.fn;         //只是调用,并没有执行
        j();         //最终还是在Window的作用域下执行

函数的属性和方法

在JavaScript中,函数是一等公民,这意味着函数可以像任何其他变量一样被传递和操作。函数对象有几个内置属性,其中包括argumentsnamelengthprototype

 function sayHello(){

        }
   console.dir(sayHello)

console.dir(sayHello)

ƒ sayHello()
arguments: null
caller: null
length: 0
name: "sayHello"
prototype: {}
[[FunctionLocation]]: f03.html:10
[[Prototype]]: ƒ ()
[[Scopes]]: Scopes[1]
  • arguments

arguments 是一个类数组对象,它包含了函数调用时传入的所有参数。这个对象在函数体内部可用,允许你访问所有参数,即使函数没有显式地定义足够多的参数。 (可以获得形参的数量)

javascript

function printArgs() {
  console.log(arguments); // 输出:[Arguments] { '0': 'foo', '1': 'bar' }
  for (let i = 0; i < arguments.length; i++) {
    console.log(arguments[i]);
  }
}

printArgs('foo', 'bar');
  • name

name 属性包含了函数的名称。这个名称是在函数被创建时赋予的,对于匿名函数,这个属性可能是空的。

function myFunction() {}
console.log(myFunction.name); // 输出:myFunction

const anonymous = function() {};
console.log(anonymous.name); // 输出:(可能是空字符串或者"anonymous",取决于JavaScript引擎)
  • length

length 属性表示函数期望接收的参数个数,即函数定义中形参的数量。(输出的是实参的数量

function myFunction(a, b, c) {}
console.log(myFunction.length); // 输出:3
  • prototype

prototype 属性是一个对象,包含了可以由通过该构造函数创建的对象继承的属性和方法。这是JavaScript原型链的基础。

function MyConstructor() {}
console.log(MyConstructor.prototype); // 输出:MyConstructor的原型对象

const instance = new MyConstructor();
console.log(instance.__proto__ === MyConstructor.prototype); // 输出:true

prototype 属性在创建构造函数时特别有用,因为它允许你定义所有实例共享的属性和方法。当你创建一个新对象时,这个对象的内部属性(__proto__)会指向其构造函数的prototype属性,从而实现原型继承。

call() , bind() , apply() 这三个函数都可以改变函数调用时 this的指向,即指定this的值。

  • call()call() 方法用于在指定的 this 值和参数的情况下 调用一个函数,即调用一个具有给定 this 值的函数,以及单独提供的参数。
var n = 123;
       function say(){
         console.log(this.n);//此时this的作用域为Window
       }
       var o = {
         n:3453
       }
        //冒充o对象 改变say函数内部的 this指向,此时this的作用域为o
       window.say.call(o)  // 输出:3453

传参:

function a(name,age){
            console.log(name,age);
            console.log(this);
            return 123;
        }
        obj = {

        }
        a.call(obj,"kimi","2")//指定了 this 的值为 obj,而调用的函数是a

参数传递

函数 a 被调用时,name 参数被赋值为 "kimi"age 参数被赋值为 "2"

this 值

由于使用了 call() 方法,并指定了 this 值为 obj,所以在函数 a 内部,this 将指向 obj 对象。

控制台输出

第一个 console.log(name, age) 将输出:kimi 2,因为这两个值是作为参数传递给函数的。

第二个 console.log(this) 将输出:{},因为 this 被设置为 obj,而 obj 是一个空对象。

返回值

函数 a 返回 123,但由于没有变量接收这个返回值,所以它不会在代码中产生进一步的影响。

function sum1(num1, num2){
            return num1+num2;
        }
        function sum2(num1, num2){
            return sum1.call(sum2,num1,num2);
        }
      console.log(sum1(5,5))  // 输出:10
      console.log(sum2(5,5))  // 输出:10
  • apply()和call()基本用法一致,但是传递参数是接收的是数组,即调用一个具有给定 this 值的函数,以及作为一个数组(或类数组对象)的参数。
var name = "jack";
        function fn(){
            console.log(this.name);
        }
        var obj = {
            name:"anndy"
        }
        fn.apply(obj)

传参:

function sum1(num1, num2){
            return num1+num2;
        }
        function sum2(num1, num2){
            return sum1.apply(sum2,[num1,num2]); // !!!!!
        }
      console.log(sum1(5,5))  // 输出:10
      console.log(sum2(5,5))  // 输出:10
  • bind()
var name = "qqtang"
        function say(){
            console.log(this.name);
        }
        var obj = {
            name:"milk"
        }
        say.bind(obj)();//say.bind(obj)返回的是一个函数,所以要加()再执行一下

上面代码的执行原理:

function fn(){
            return function(){}
            console.log(123);
        }
        fn()();

传参:

var name = "qqtang"
        function say(name,age){
            console.log(this.name);
            console.log(name,age);
        }
        var obj = {
            name:"milk"
        }
        say.bind(obj,"ross",21)()

//或者
var name = "qqtang"
        function say(name,age){
            console.log(this.name);
            console.log(name,age);
        }
        var obj = {
            name:"milk"
        }
        say.bind(obj,"ross")(21)// bind()的科里化特性

bind() 函数的一个特性是“柯里化”(Currying),这是指将多参数的函数转换成一系列使用一个或两个参数的函数。bind() 方法可以部分应用一个函数,即预先填充一个或多个参数,然后返回一个新函数,这个新函数可以接收剩余的参数。

总结:

call():当你需要立即执行函数,并且参数列表已知时使用。

apply():当你需要立即执行函数,但参数列表是动态的或者存储在数组中时使用。

bind():当你需要创建一个新的函数,其 this 值被预设,或者需要预设参数时使用。bind() 通常用于事件处理和回调函数中,以确保 this 的值是预期的对象。

这三个方法都是 JavaScript 函数原型上的方法,所以它们可以被任何函数使用。

闭包

1. 定义和形成

闭包是由函数以及创建该函数时所处的词法环境(作用域)组合而成的。当一个函数在其外部函数之外被引用时,就会形成闭包。

闭包是一种概念但是在]S里面我们可以通过具体的程序来表达闭包的这种概念
闭包和函数作用域有关系,闭包就是一个函数内部的函数,内层的函数可以访问到外层的函数的作用域中的变量
闭包可以将一个变量驻留到内存中不会被垃圾回收机制回收,因为某个变量还被引用所以不会回收
可以让外部访问到局部变量,也可以模拟一个私有作用域。

闭包(Closure)是JavaScript中一个非常重要的概念,它指的是一个函数能够访问其定义时的作用域链,即使在其定义的作用域之外执行。闭包通常用于创建私有变量和封装函数,:

function outerFunction() {
  var outerVar = "I am outer";

  return function innerFunction() {
    console.log(outerVar);
  };
}

const myClosure = outerFunction();
myClosure(); // 输出:I am outer

在这个例子中,innerFunction 能够访问 outerFunction 的作用域中的 outerVar 变量,即使 innerFunctionouterFunction 外部被调用。

2. 私有变量

闭包可以用来创建私有变量,这些变量只能在闭包内部被访问和修改,从而隐藏实现细节。

function createCounter() {
  let count = 0;
  return function() {
    count += 1;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 输出:1
console.log(counter()); // 输出:2
var Counter = function(){
            var privateCounter  = 0;
            function changeby(n){
                privateCounter += n;
            }
            return {
                increment:function(){
                    changeby(1);
                },
                decrment:function(){
                    changeby(-1)
                },
                value:function(){
                    return privateCounter; 
                }
            }
        }();
        console.log(Counter.value())  //输出:0
        Counter.increment()
        console.log(Counter.value())  //输出:1
        Counter.decrment()
        console.log(Counter.value())   //输出:0
3. 延长变量的生命周期

闭包可以延长变量的生命周期,即使外部函数已经执行完毕,闭包中的变量仍然可以被访问。

function dataKeeper() {
  var importantData = "This data is important";
  window.leakData = function() {
    return importantData;
  };
}

dataKeeper();
console.log(window.leakData()); // 输出:This data is important
4. 函数作为值

在JavaScript中,函数是一等公民,这意味着函数可以像任何其他值一样被传递和返回,这是形成闭包的基础。

function factory() {
  var res = [];
  for (var i = 0; i < 5; i++) {
    res[i] = function() {
      return i;
    };
  }
  return res;
}

const array = factory();
for (var i = 0; i < array.length; i++) {
  console.log(array[i]()); // 输出:5, 5, 5, 5, 5
}
var arr = [];
     for(var i = 1 ; i<=5 ; i++){
          arr[i-1] = function(num){
              return function(){
                  return num
              };
           }(i); //传参,把i传给num
      }

    for(var j = 0 ; j<arr.length ; j++){
       console.log(arr[j]());
    }   //输出:1, 2, 3, 4, 5
5. 闭包和内存泄漏

由于闭包会持续访问外部函数的变量,这可能导致内存泄漏,尤其是在循环和DOM元素事件处理程序中。

for (var i = 0; i < 100; i++) {
  document.body.addEventListener('click', (function(index) {
    return function() {
      console.log(index);
    };
  })(i));
}

在这个例子中,每个事件监听器都创建了一个闭包,这些闭包持续引用了循环变量 i,导致 i 的值无法被垃圾回收。

6. 闭包的应用

闭包在JavaScript中有着广泛的应用,包括模块模式、事件处理程序、回调函数等。

  • 模块模式:使用闭包来实现模块的私有变量和公共接口。
  • 事件处理程序:在事件处理程序中使用闭包来访问和修改DOM元素的状态。
  • 回调函数:在异步编程中使用闭包来保存和传递状态。

理解闭包是掌握JavaScript高级特性的关键,它不仅涉及到函数的执行,还涉及到内存管理和代码的封装。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值