js三座大山之函数1

563 阅读7分钟

js三座大山

一:函数式编程
js三座大山之函数1 
js三座大山之函数-静态词法作用域
js三座大山之函数-运行时this的四种指向

二:面向对象编程
js三座大山之对象,继承,类,原型链

三:异步编程:
js三座大山之异步一单线程,event loope,宏任务&微任务
js三座大山之异步二异步方案
js三座大山之异步三promise本质
js三座大山之异步四-Promise的同步调用消除异步的传染性
js三座大山之异步五基于异步的js性能优化
js三座大山之异步六实现微任务的N种方式
js三座大山之异步七实现宏任务的N种方式

第一篇:

如何理解函数:函数是可运行的对象。

函数的声明方式:

函数声明:

function myFunction() {
  return 'Hello, world!';
}

函数表达式

var myFunctionExpression = function() {
  return 'Hello, world!';
};

区别:

  1. 提升(Hoisting):函数声明在整个作用域中都是可见的,因为它们会被提升到作用域的顶部。这意味着你可以在声明之前调用这个函数。而函数表达式(包括具名和匿名)在声明之前是不可见的,因为它们不会被提升。
// 函数声明
console.log(myFunction()); // 输出:"Hello, world!"
function myFunction() {
  return 'Hello, world!';
}

// 函数表达式
console.log(myFunctionExpression()); // 报错:myFunctionExpression is not a function
var myFunctionExpression = function() {
  return 'Hello, world!';
};

2:可选的函数名:函数声明必须有一个函数名,而函数表达式可以是匿名的,不过大部分情况下js引擎是可以推断出函数名的。

// 函数声明
function myFunction() {
  return 'Hello, world!';
}
// 匿名函数表达式
(function() {
  return 'Hello, world!';
}());

// 可以推断出的函数表达式
var test = function(){
 return 'Hello, world!';
}
consoel.log(test.name) // 'test'
  1. 作用域:函数声明的名称在整个作用域中都是可见的。而具名函数表达式的名称只在函数体内部有效&只读不可更改。
// 函数声明
function myFunction() {
  return 'Hello, world!';
}
console.log(typeof myFunction); // 输出:"function"

// 具名函数表达式
var myFunctionExpression = function innerFunction() {
  return 'Hello, world!';
};
console.log(typeof innerFunction); // 输出:"undefined"

函数的组成结构:

- 函数名: 可选 但是确定后只读不可改变

function myFunction() {
  return 'Hello, world!';
}
const origin = myFunction;
myFunction = 123
conosole.log(origin.name) // 'myFunction'

这段代码可以解释为 声明了一个myFunction的变量 然后开辟一段堆内存存储函数对象,里面有name,length,函数字符串,内部定义的变量,作用域链等上下文环境。然后将myFunction这个变量指向这段内存。这样后续就可以访问到这个函数了。需要注意的是 堆内存里面函数对象的name名字也是’myFunction‘并且是只读的永远不会在改变。 所以平时我们说的函数名其实是指引用函数的变量。真正的函数名只能通过name属性或者在函数内部访问。函数的 name 属性可用于在调试工具或错误消息中标识该函数。它对语言本身没有任何意义。

// case1:具名iife表达式 立即执行的函数表达式外部没有指向函数的引用
(function fn(name) {
  fn = '123' // 这里想要在内部改变函数名 无效因为是只读的
  console.log(fn); // 打印函数本身
})();

// case2:函数声明
function fn(){
  fn = '123' // 这里是改变了指向函数引用的变量fn 所以成功了
  console.log(fn); // 打印123
}
fn();


var myFunctionExpression = function innerFunction() {
    innerFunction = '123'
    myFunctionExpression = '123'
    console.log('test myFunctionExpression', myFunctionExpression); //  打印'123'
    console.log('test innerFunction', innerFunction); // 打印函数本身
};
myFunctionExpression()

在函数内部 指向函数引用的变量fn和函数名称fn同时存在且命名相同时 函数的引用变量fn优先级更高。

- 函数参数:形参 实参 默认参数 剩余参数 arguments(已弃用:  不再推荐使用该特性)

  1. 形参:形参是在函数定义时声明的参数,它们是函数内部的局部变量。当函数被调用时,形参会接收实参的值,可以通过函数的length属性获取。
// 依次列出函数的形参
function myFunction(name, age) {
  return name + age;
}
myFunction.length // 2

// 默认参数影响形参
function myFunction(name, age = 1) {
  return name + age;
}
myFunction.length // 1

// 剩余参数影响形参
function myFunction(name, ...info) {
  return name + age;
}
myFunction.length // 1

所以在使用函数的形参即lenght属性来做判断时 要慎重 因为可能因为函数的写法不同导致功能异常

  1. 实参:实参是在函数调用时传递给函数的值,它们会赋值给函数的形参。

  2. 默认参数:在ES6中,函数可以有默认参数。如果在函数调用时没有提供实参,或者实参的值是undefined,那么形参会使用默认参数的值。

function greet(name = 'World') { // 'World' 是默认参数
  console.log('Hello, ' + name);
}
greet(); // 输出:"Hello, World"
  1. 剩余参数(Rest Parameters):在ES6中,可以使用...语法定义剩余参数。剩余参数是一个数组,它包含了从它开始到所有后面的实参。
function greet(name, ...others) { // others 是剩余参数 是一个真正的数组
  console.log('Hello, ' + name);
  console.log('Others: ' + others.join(', '));
}

greet('John', 'Paul', 'George', 'Ringo'); // 输出:"Hello, John" 和 "Others: Paul, George, Ringo"
  1. arguments对象(不推荐):在函数内部,可以使用arguments对象访问所有的实参,无论函数是否定义了对应的形参。arguments是一个类数组对象,它有一个length属性和索引属性,但是没有数组的方法。
function greet() {
  for (var i = 0; i < arguments.length; i++) {
    console.log(arguments[i]);
  }
}
greet('John', 'Paul', 'George', 'Ringo'); // 输出:"John" "Paul" "George" "Ringo"

箭头函数没有这个属性

- 函数体: 一段逻辑代码

- 返回值:符合js规范的数据类型

函数可以通过return语句返回一个值。这个返回的值可以是任何类型的数据,包括基本类型(如数字、字符串、布尔值等)和复杂类型(如对象、数组、函数等)。当函数执行到return语句时,函数的执行会立即停止,并且返回return语句后面的值。如果return语句没有跟任何值,或者函数没有return语句,那么函数会返回undefined。

函数的表现形式:

  1. 普通函数:默认this是动态的 执行调用对象。
function greet(name) {
  console.log('Hello, ' + name);
}
  1. 对象方法:默认this指向obj对象。
var obj = {
  greet: function(name) {
    console.log('Hello, ' + name);
  }
};
  1. 构造函数:默认this指向新创建的对象。
function Person(name) {
  this.name = name;
}
var john = new Person('John');
  1. 箭头函数:ES6引入了箭头函数,它是一种更简洁的函数语法。箭头函数没有自己的thisargumentssupernew.target这些值由外层作用域的非箭头函数决定
    注意: 只有函数才能生成新的作用域
var greet = (name) => {
  console.log('Hello, ' + name);
};


const obj = {
    name: 'hahha',
    run(){
        const say = (name) => {
          console.log(this); // this指向obj
        }; 
      say();
    }
}
obj.run()


const obj = {
    name: 'hahha',
    run:()=>{
        console.log('run this', this)
    },
    say:function(){
        console.log('say this', this)
    }
}
obj.run() // this指向全局window 因为对象不生成作用域 只有函数才能生成新的作用域
obj.say() // this指向obj 
  1. 立即执行函数iife
(function(name) {
  console.log('Hello, ' + name);
})('World');

// 具名立即执行函数
(function fn(name) {
  console.log(fn); // 打印函数 
  console.log(name);
})('World');
typeof fn // 'undefined' 实际上是一个函数表达式所以这里访问不到函数名字
  1. 闭包函数: 控制变量访问权限,延长变量的声明周期,因为声明周期被延长了 所以可能导致内存泄漏。因为内存泄漏的本质就是:对象的生命周期大于对象的作用域,导致对象没被及时释放.
function fn(...list){
    let params = [...list]
    return function(...rest){
        params = [...params, ...rest]
        return params;
    }
    
}
  1. 函数对象:
function fn(...list){
    console.log('fn:', list)
}
fn.test = 'test' // 函数就是一个对象,所以可以动态添加属性。
function fn(){
    console.log('fn:', this)
}
let obj = {name: 'abc'}
fn.call(obj) // 函数是一个对象 所以在fn实例上找不到call方法后会向Function.prototype中寻找。
  1. 生成器函数(Generator Function):ES6引入了生成器函数,它可以返回一个生成器对象。生成器对象可以按需产生一系列的值。
function* gen(){
    const arr = [];
    let i = 0;
    while(i<10){
        arr.push(i)
        yield i
        i++;
    }
    return arr;
}
const arr = gen(); // 返回迭代器对象

arr.next()

参考:

  1. developer.mozilla.org/zh-CN/docs/…
  2. es6.ruanyifeng.com/#docs/funct…