Bootstrap

JavaSrcipt 函数高级

一  原型与原型链

prototype

每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象或者显示原型)

原型对象prototype中有一个属性constructor, 它指向函数对象

 function a(){}
 console.log(typeof a,typeof Date)
 console.log(a.prototype, Date.prototype)
 console.log(a.prototype.constructor === a,Date.prototype.constructor === Date)
   

 

 注意仅函数有

   let arr = []
   let obj = []
   console.log(arr.prototype) // undefined
   console.log(obj.prototype) // undefined

可以给原型对象添加属性(一般都是方法)

能让后代拥有同样的方法

function F() {}
      F.prototype.age = 12; //添加属性
      F.prototype.setAge = function (age) {
        // 添加方法
        this.age = age;
      };
  // 创建函数的实例对象
  var f = new F();
  console.log(f.age);//12
  f.setAge(23);
  console.log(f.age);//23

显式原型与隐式原型

显式原型通常指的是通过构造函数来定义对象的原型。每个构造函数都有一个 prototype 属性,这个属性指向一个对象,用于设置通过该构造函数创建的实例对象的原型

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

Person.prototype.sayHello = function() {
  console.log('Hello, ' + this.name);
};

const person = new Person('Alice');
person.sayHello(); // Hello, Alice

隐式原型指的是通过实例对象访问的 __proto__ 属性。每个对象都有一个隐式原型,它指向该对象的构造函数的 prototype 属性。虽然 __proto__ 现在已经被标准化,但它不是官方推荐的访问方式(推荐使用 Object.getPrototypeOf())。

const person = new Person('Bob');
console.log(person.__proto__ === Person.prototype); // true

原型链

原型链是 JavaScript 实现继承的核心机制。它是通过对象的原型(prototype__proto__)属性形成的一种链式结构,用于实现属性和方法的继承与查找。

function A() {
  this.name = "A";
}

A.prototype.sayHello = function () {
  console.log("Hello from A");
};

function B() {
  this.age = 30;
}

B.prototype = new A(); // B 的原型设置为 A 的实例,实现继承
B.prototype.constructor = B; // 修复 constructor 指向

B.prototype.sayHi = function () {
  console.log("Hi from B");
};

const b = new B();

console.log(b.name); // "A" - 从 A 的原型链上找到
b.sayHello(); // "Hello from A" - 从 A 的原型链上找到
b.sayHi(); // "Hi from B" - B 自己的方法

console.log(b.__proto__ === B.prototype); // true
console.log(B.prototype.__proto__ === A.prototype); // true
console.log(A.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null

属性问题

读取会从原型链找,设置不会

 function Person(name, age) {
    this.name = name;
    this.age = age;
  }
  Person.prototype.setName = function (name) {
    this.name = name;
  }
  Person.prototype.sex = '男';

  var p1 = new Person('Tom', 12)
  p1.setName('Jack')
  console.log(p1.name, p1.age, p1.sex,111)

  p1.sex = '女'
  console.log(p1.name, p1.age, p1.sex,222)

instanceof 运算符

instanceof 是 JavaScript 中用于检查对象是否是某个构造函数的实例的运算符。它通过检查对象的原型链,判断某个对象是否继承自某个构造函数的 prototype

function A() {}
const a = new A();

console.log(a instanceof A); // true
console.log(a instanceof Object); // true

二 闭包函数

理解闭包

如何产生闭包?

* 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 外部函数调用,就产生了闭包

产生闭包的条件?

  • 函数嵌套
  • 内部函数引用了外部函数的数据(变量/函数)
  • 调用了外部的函数

常见的闭包

将函数作为另一个函数的返回值

function outerFunction() {
  let count = 0; // 局部变量
  return function innerFunction() {
    count++; // innerFunction 是闭包
    console.log(count);
  };
}

const closureFunction = outerFunction();
closureFunction(); // 1
closureFunction(); // 2

将函数作为实参传递给另一个函数调用

 function showMsgDelay(msg, time) {
    setTimeout(function () {
      console.log(msg)
    }, time)
  }
  showMsgDelay('hello', 1000)
  //上面的代码中,闭包是里面的function,因为它是嵌套的子函数,而且引用了外部函数的变量msg。

 闭包的作用

(1). 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)

(2). 让函数外部可以操作(读写)到函数内部的数据(变量/函数)


      function fun1() {
        var a = 3;

        function fun2() {
          a++; //引用外部函数的变量--->产生闭包
          console.log(a);
        }

        return fun2;
      }
      var f = fun1(); //由于f引用着内部的函数-->内部函数以及闭包都没有成为垃圾对象

      f(); //间接操作了函数内部的局部变量
      f();

闭包的缺点

(1). 缺点

* 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长

* 容易造成内存泄露

(2). 解决

* 能不用闭包就不用

* 及时释放

内存溢出与内存泄露

内存溢出

  •  一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误

 内存泄露

  • 占用的内存没有及时释放
  • 内存泄露积累多了就容易导致内存溢出
  • 常见的内存泄露:
    • 意外的全局变量
    • 没有及时清理的事件回调函数
    • 闭包
    • 没有及时清理的定时器 setTimeout,setInterval
    • 大量循环打印控制台日志

 

三 递归函数

递归是函数的高级用法,本质上是函数自已调用自已,它的行为非常类似循环

递归函数的特性

(1). 重复执行

(2).调用自身

(3). 【必须】要有条件控制,避免死循环,如果递归函数没有条件控制,那么他就是死循环

递归本身是一种循环操作,简单情况下可以替换循环语句的使用

注意:递归慎用,能用循环解决的事情,尽量别用递归


      // 递归函数 :在函数内部调用自己,通过条件控制避免死循环
      // 一直造成foo函数重复调用-- 死循环
      var i = 0;
      function foo() {
        if (i >= 3) return;//限制条件
        i++;
        console.log("递归函数");
        foo(); // 2.内部调用自己
      }
      foo(); // 1.外部调用

      // 递归三特性-- 重复执行 / 调用自身 / 条件控制避免死循环!

递归函数常用案例

斐波拉契数列

  <!-- 经典案例2:斐波拉契数列
        1,1,2,3,5,8,13,21,34,55,89...求第n项 -->
    <script>
      //递归方法
      function fib(n) {
        if (n === 1 || n === 2) return n;
        return fib(n - 1) + fib(n - 2);
      }
      console.log(fib(10)); //34

      //非递归方法
      function fib(n) {
        var a = 0;
        var b = 1;
        var c = a + b;
        for (var i = 3; i < n; i++) {
          a = b;
          b = c;
          c = a + b;
        }
        return c;
      }
      console.log(fib(10)); //34
    </script>

;