JavaScript 进阶

# JavaScript 进阶

# 原型链

在 JavaScript 中,每当定义一个函数数据类型(普通函数、类)时候,都会天生自带一个 prototype 属性,这个属性指向函数的原型对象。

当函数经过new调用时,这个函数就成为了构造函数,返回一个全新的实例对象,这个实例对象有一个__proto__属性,指向构造函数的原型对象。

原型链是继承的主要实现方式,通过这种方式继承多个引用类型的属性和方法。

# 描述一下原型链

JavaScript 对象通过__proto__ 指向父类构造函数的原型对象,父类的原型对象又通过__proto__指向他的父类原型对象,就这样直到指向 Object 对象为止,Object 的原型对象的__proto__指向 null,这样就形成了一个原型指向的链条, 即原型链。

moliy_prototype_chain

# JS 面向对象之寄生组合式继承

function inheritPrototype(SubType, SuperType) {
  SubType.prototype = Objec.create(SuperType.prototype);
  Object.defineProperty(SubType.prototype, 'constructor', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: SubType,
  });
}

function Person(name, age, friends) {
  this.name = name;
  this.age = age;
  this.friends = friends;
}

Person.prototype.running = function () {
  console.log('running~');
};

Person.prototype.eating = function () {
  console.log('eating~');
};

function Student(name, age, friends, sno, score) {
  Person.call(this, name, age, friends);
  this.sno = sno;
  this.score = score;
}

inheritPrototype(Student, Person);

Student.prototype.studying = function () {
  console.log('studying~');
};

// 测试
var stu = new Student('why', 18, ['kobe'], 111, 100);
console.log(stu);
stu.studying();
stu.running();
stu.eating();

# 闭包

我理解的闭包,就是能够访问除了本身作用域以外的其他作用域函数

两大作用:

  • 保存,只要当前上下文不被释放,则存储的这些私有变量也不会被释放
  • 保护,在当前作用域中,维护了一个私有变量,且这个变量不会再改变

就像我旅游,身上背了一个包,里面有瓶水我喝完就丢掉了,但是门票我得一直拿着,因为后续还要用。

# JS 堆栈内存释放

  • 堆内存:存储引用类型值,对象类型就是键值对,函数就是代码字符串。
  • 堆内存释放:将引用类型的空间地址变量赋值成 null,或没有变量占用堆内存了浏览器就会释放掉这个地址
  • 栈内存:提供代码执行的环境和存储基本类型值。
  • 栈内存释放:一般当函数执行完后函数的私有作用域就会被释放掉。

但栈内存的释放也有特殊情况:

① 函数执行完,但是函数的私有作用域内有内容被栈外的变量还在使用的,栈内存就不能释放里面的基本值也就不会被释放。

② 全局下的栈内存只有页面被关闭的时候才会被释放

# 产生闭包的场景

  1. 返回函数的函数

  2. 函数作为参数传进另一个函数

  3. 在任何异步事件中(宏任务、微任务),只要使用了回调,实际上使用的就是闭包。

    原因:异步事件在出栈时只能去上层作用域查找变量。

  4. 自执行函数,保存了全局作用域window当前函数的作用域

    var a = 2;
    (function IIFE() {
      // 输出2
      console.log(a);
    })();
    
  5. 比较常见的闭包实现有:防抖、节流、柯里化

# 如何解决下面的循环输出问题?

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i);
  }, 0);
}

为什么会全部输出 6?如何改进,让它输出 1,2,3,4,5?(方法越多越好)

解决方法:

1、利用 IIFE(立即执行函数表达式)当每次 for 循环时,把此时的 i 变量传递到定时器中

for (var i = 1; i <= 5; i++) {
  (function (j) {
    setTimeout(function timer() {
      console.log(j);
    }, 0);
  })(i);
}

2、给定时器传入第三个参数, 作为 timer 函数的第一个函数参数

for (var i = 1; i <= 5; i++) {
  setTimeout(
    function timer(j) {
      console.log(j);
    },
    0,
    i
  );
}

3、使用 ES6 中的 let

for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i);
  }, 0);
}

let 使 JS 发生革命性的变化,让 JS 有函数作用域变为了块级作用域,用 let 后作用域链不复存在。代码的作用域以块级为单位,以上面代码为例:

// i = 1
{
  setTimeout(function timer(){
    console.log(1)
  },0)
}
// i = 2
{
  setTimeout(function timer(){
    console.log(2)
  },0)
}
// i = 3
...

因此能输出正确的结果。

# 怎么检查内存泄露


# 深、浅拷贝

我的理解 (opens new window)

# 深、浅比较

我的理解 (opens new window)


# 参考文章

  1. 闭包分析 (opens new window)
  2. js 灵魂之问(上) (opens new window)
  3. js 灵魂之问(下) (opens new window)
  4. 非常全面 (opens new window)