this 与闭包

109 阅读9分钟

主讲:麓一

专业术语

  • 常量、变量、数据类型
  • 形参、实参
  • 匿名函数、具名函数、自执行函数
  • 函数声明、函数表达式
  • 堆、栈
  • 同步、异步、进程、线程

执行上下文

当函数执行时,会创建⼀个称为执行上下文(execution contex)的环境,分为 创建执行 2 个阶段。

创建阶段

创建阶段,指函数被调用但还未执行任何代码时,此时创建一个拥有 3 个属性的对象:

executionContext = {
  scopeChain: {}, // 创建作用域链(scope chain)
  variableObject: {}, // 初始化变量、函数、形参
  this: {}, // 指定this
};

代码执行阶段

代码执行阶段主要的工作是:

  1. 分配变量、函数的引用,赋值;
  2. 执行代码;

执行上下文栈

  • 浏览器中的 JS 解释器是单线程的,相当于浏览器中同一时间只能做一件事情;
  • 代码中只有一个全局执行上下文,和无数个函数执行上下文,这些组成了执行上下文栈(Execution Stack);
  • 一个函数的执行上下文,在函数执行完毕后,会被移出执行上下文栈;
function c() {
  console.log('ok');
}

function a() {
  function b() {
    c();
  }
  b();
}

a();

这个例子的执行上下文栈是这样的:

image-20210511234631575.png

作用域

  • 作用域,简单来说,就是在特定的场景下,特定范围内,查找变量的一套原则;

    • 一般情况下,我们特指:词法作用域、静态作用域;

    • 一般是代码层面上的;

  • 分类:

    • 全局作用域;
    • 函数作用域;
      • 在函数内声明的所有变量,在函数体内是始终可见的,可以在整个函数范围内复用;
    • 块作用域;
      • 是一个用来对之前的最小授权原则进行扩展的工具,将代码在函数中隐藏信息扩展为在块中;
// example 1
function foo(a) {
  console.log(a); // 2
}
foo(2);

// example 2
function bar() {
  var b = 5;
}

function foo(a) {
  console.log(a + b); // NaN
  var b = 3;
}
foo(2);

function foo2(a) {
  console.log(a + b); // ReferenceError: Cannot access 'b' before initialization
  let b = 3;
}
foo2(2);
  • foobar 中的 b ,分属于两个独立且不同的作用域;
  • 为什么 Cannot access 'b' before initialization
    • let 作为块级作用域,会存在暂时性死区;
  • 为什么 NaN
    • 变量提升;

块级作用域和暂时性死区

  • 哪些会构成块级作用域:

    • if
    • for
    • {...}
  • 暂时性死区:

    • let 声明的变量的块的第一行,到声明变量之间的这个区域,被称为暂时性死区;
    • 暂时性死区存在时,会让 let 绑定这个区域,在这个区域内,无法执行该变量的其他声明;

函数表达式

JS 是如何运行起来的?

  • 代码的预编译阶段:
    • 会对变量的内存空间进行分配;
    • 对变量声明进行提升,但是值为 undefined
    • 对所有的非表达式的声明进行提升;
var bar = function () {
  console.log('bar2');
};

function bar() {
  console.log('bar1');
}

// 相当于是 ---------->
function bar() {
  console.log('bar1');
}
var bar;
bar = function () {
  console.log('bar2');
};
bar();

JS 中有全局作用域、函数作用域,ES6 中又增加了块级作用域。作用域的最大用途就是隔离变量和函数,并控制他们的生命周期。作用域是在函数执行上下文【创建时】定义好的,不是函数执行时定义的。

函数在定义的时候,就已经确定了函数体内部自由变量的作用域。**js 没有块级作用域,除了全局作用域外,只有函数才能创建作用域。**作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

// 定义时!!!
// 一个函数找作用域链的时候,是找定义时候的作用域链
function demo(num) {
  var getData = function getData() {};
  console.log('myName >>> ', myName); // 报错:ReferenceError: myName is not defined
  function c() {}
}

function outer() {
  var myName = 'xiaowa';
  demo(100);
}

outer();
function a() {
  return function b() {
    var myname = 'b';
    console.log(myname); // b
  };
}

function c() {
  var myname = 'c';
  b();
}

var b = a();
c();

/* 
  输出结果:b
*/
// 块级作用域
// 去掉函数 b 中的 myname 声明后
function a() {
  return function b() {
    // var myname = 'b';
    console.log(myname); // 这⾥会报错:ReferenceError: myname is not defined
  };
}

function c() {
  var myname = 'c';
  b();
}

var b = a();
c();

let(bable 编译)

function demo(num) {
  console.log('name >>>> ', name);
  // const let
  let name = 'xiaowa';
}

demo(100);

babel 编译后结果:会把 let/const 编译成 var

/******/ (() => {
  // webpackBootstrap
  var __webpack_exports__ = {};
  function demo(num) {
    console.log('name >>>> ', name); // const let

    var name = 'xiaowa';
  }

  demo(100);
  /******/
})();

let 在块级作用域中,babel 编译后:

function demo(num) {
  {
    let myName = 'xiaowa';
    console.log('myName >>>> ', myName);
  }
  console.log('myName >>>> ', myName); // 编译后,此处的输出会报错,ReferenceError: myName is not defined
}

demo(100);
/******/ (() => {
  // webpackBootstrap
  var __webpack_exports__ = {};
  function demo(num) {
    {
      var _myName = 'xiaowa';
      console.log('myName >>>> ', _myName);
    }
    console.log('myName >>>> ', myName);
  }

  demo(100);
  /******/
})();

作用域链

当一个块或函数嵌套在另一个块或函数中时,就会生了作用域的嵌套。在当前函数中如果 JS 引擎无法找到某个变量,就会往上一级嵌套的作用域中去寻找,直到找到该变量或抵达全局作用域,这样的链式关系就称为 作用域链Scope Chain)。

作用域有上下级关系,上下级关系的确定就看函数在哪个作用域下创建的,当代码在一个环境中执行,会创建变量对象的一个作用域链。当访问变量时,会一级一级向上寻找变量定义,直到找到它。若一直寻找到全局作用域还找不到就会报 'xxx is not defined' 的错误。

闭包

函数嵌套函数时,内层函数引用了外层函数作用域下的变量,并且内层函数,在全局环境下可访问,就形成了闭包。 当函数的执行上下文,没有在原本的词法作用域内,就形成了闭包。

高级程序设计三中:闭包是指有权访问另外一个函数作用域中的变量的函数,可以理解为(能够读取其他函数内部变量的函数)。

function outer() {
  var top = xxxx;
  function inner() {
    xxx.innerHTML = top;
  }
}

内部函数访问到外部的变量了,就叫闭包了。

function outer() {
  var top = 'yuanxin';
  function inner() {
    console.log('top >>>> ', top);
  }
  inner();
}

outer();

/* 
  内部函数访问到外部的变量了,就叫闭包了
*/
function outer() {
  var top = 'yuanxin';
  let count = 0;
  return function () {
    count++;
    console.log('top >>>> ', top, count);
  };
}

let func = outer();
func();
func();

/* 
  top >>>>  yuanxin 1
  top >>>>  yuanxin 2
*/

平时用在哪?

  • 封装私有变量;
  • 存储变量;

封装私有变量(AMD 的框架等都使用)

普通的定义类的方式

// 普通的定义类的方式
function Person() {
  this._attackVolume = 100;
}

Person.prototype = {
  attack() {
    console.log(this._attackVolume - 10); // 90
  },
};

var person = new Person();
console.log(person._attackVolume); // 100
person.attack();

工厂方法

构造函数和工厂函数都可以创造对象,构造函数需要 new 一个对象,工厂函数不用,这两种方式没有区别。

// 工厂方法
// 构造函数和工厂函数都可以创造对象,构造函数需要 new 一个对象,工厂函数不用,这两种方式没有区别
function Person() {
  var _attackVolume = 100;

  return {
    attack() {
      console.log(_attackVolume); // 100
      console.log(this._attackVolume); // undefined
    },
  };
}

var person = Person();
console.log(person._attackVolume); // undefined
person.attack();

存储变量

// 封装的时候
function getListDataManager() {
  // 外层 scope 中定义⼀个变量
  let localData = null;
  return {
    getData() {
      // ⾥⾯的函数使⽤外层的变量,⽽且是反复使⽤
      if (localData) {
        return Promise.resolve(localData);
      }
      return fetch('xxxx').then(data => (localData = data.json()));
    },
  };
}

// ⽤的时候
const listDataManager = getListDataManager();

button.onclick = () => {
  // 每次都会去获取数据,但是有可能是获取的缓存的数据
  text.innerHTML = listDataManager.getData();
};

window.onscroll = () => {
  // 每次都会去获取数据,但是有可能是获取的缓存的数据
  text.innerHTML = listDataManager.getData();
};

拆分执行

function createIncrement() {
  let count = 0;

  function increment() {
    count++;
  }

  let message = `count is ${count}`;

  function log() {
    console.log(message); // count is 0
    console.log(count); // 3
  }

  return [increment, log];
}
const [increment, log] = createIncrement();

increment();
increment();
increment();
log();

实现私有变量

function createStack() {
  return {
    items: [],
    push(item) {
      this.items.push(item);
      console.log(this.items);
    },
  };
}

const stack = {
  items: [],
  push: function () {},
};

// 对外不暴露变量
function createStack2() {
  const items = [];
  return {
    push(item) {
      items.push(item);
      console.log(items);
    },
  };
}

let fn = createStack2();
fn.push(1);
fn.push(2);

this 的指向

普通函数中的 this

this 的概念是:thisJavaScript 的一个关键字,他是指函数**【执行过程】**中,自动生成的一个内部对象,是指 当前的对象,只在当前函数内部使用。

this 对象是在【运行时】基于函数的执行环境绑定的:在全局函数中,this 指向的是 Window;当函数被作为某个对象的方法调用时,this 就等于那个对象。

箭头函数的 this

箭头函数的 this 定义:箭头函数的 this 是在【定义函数时】绑定的,不是在执行过程中绑定的。简单的说,函数在定义时,this 就继承了定义函数的对象。


一共有 5 种场景。

场景 1:函数直接调用时(函数表达式、匿名函数、嵌套函数)

  • 自然【执行时】,就是全局或者 undefined(严格模式下);
// 函数表达式
function myfunc() {
  console.log(this); // this 是全局(window)
}

// 自然执行时,就是全局或者 undefined(严格模式下)
myfunc();

// 匿名函数
(function () {
  console.log(this); // this 是全局(window)
})();
// 函数表达式
function myfunc() {
  'use strict'; // 严格模式下 this 是 undefined
  console.log(this); // undefined
}

// 自然执行时,就是全局或者 undefined(严格模式下)
myfunc();

// 匿名函数
(function () {
  'use strict'; // 严格模式下 this 是 undefined
  console.log(this); // undefined
})();

场景 2:函数被别人调用时

  • 【执行时】,被别人调用时,this 就是调动的对象;
function myfunc() {
  console.log(this); // this 是对象 a
}

var a = {
  name: 'a',
  myfunc: myfunc,
};

// 执行时,被别人调用时,this 就是调动的对象
a.myfunc();
function fn() {
  console.log('隐式绑定:', this.a); // 隐式绑定: 1
}
const obj = {
  a: 1,
  fn,
};

obj.fn = fn;
obj.fn();

面试题:

const foo = {
  bar: 10,
  fn: function () {
    console.log(this.bar); // undefined
    console.log(this); // 全局(window / global)
  },
};

// 取出
let fn1 = foo.fn;
// 执行
fn1(); // 函数直接调用,this 指向全局
追问1:如何改变指向
const o1 = {
  text: 'o1',
  fn: function () {
    // 直接使用上下文 - 传统分活
    return this.text;
  },
};

const o2 = {
  text: 'o2',
  fn: function () {
    // 呼叫领导执行 - 部门协作
    return o1.fn();
  },
};

const o3 = {
  text: 'o3',
  fn: function () {
    // 直接内部构造 - 公共人
    let fn = o1.fn;
    return fn();
  },
};

console.log('o1fn >>>> ', o1.fn()); // o1
console.log('o2fn >>>> ', o2.fn()); // o1
console.log('o3fn >>>> ', o3.fn()); // undefined
追问:现在我要将 console.log('o2fn', o2.fn()) 的结果是 o2
// 1. 人为干涉,改变this - bind/call/apply
// 2. 不许改变 this
const o1 = {
  text: 'o1',
  fn: function () {
    return this.text;
  },
};

const o2 = {
  text: 'o2',
  fn: o1.fn,
};

console.log('o2fn >>>> ', o2.fn()); // o2
// this 指向最后调用他的对象,在 fn 执行时,o1.fn 抢过来挂载在自己 o2fn 上即可

场景 3:new 一个实例时

  • 【执行时】,new 的时候,this 就是 new 出来的对象;
function Person(name) {
  this.name = name;
  console.log(this); // this 是指实例 p
}

// 执行时,new 的时候,this 就是 new 出来的对象
var p = new Person('zhaowa');
console.log(p.name); // zhaowa
function getColor(color) {
  this.color = color;
  console.log(this); // Window
}

function Car(name, color) {
  console.log(this); // this 指的是实例 car
  this.name = name;
  getColor(color); // 直接调用,getColor 方法中的 this 指向全局 Window
}

var car = new Car('卡车', '绿色');
console.log(car.name, car.color);

/* 
  输出:卡车 undefined
*/

面试题:

class Course {
  constructor(name) {
    this.name = name;
    console.log('构造函数中的this >>>> ', this); // Course { name: 'course this' }
  }

  test() {
    console.log('类方法中的this >>>> ', this); // Course { name: 'course this' }
  }
}

const course = new Course('course this');
course.test();
追问:类中异步方法,this 有区别吗?
class Course {
  constructor(name) {
    this.name = name;
    console.log('构造函数中的this >>>> ', this); // Course { name: 'course this' }
  }

  test() {
    console.log('类方法中的this >>>> ', this); // Course { name: 'course this' }
  }
  asyncTest() {
    console.log('异步方法外 >>>> ', this); // Course { name: 'course this' }
    setTimeout(function () {
      console.log('异步方法内 >>>> ', this); // 全局 window
    }, 100);
  }
}

const course = new Course('course this');
course.test();
course.asyncTest();

场景 4:apply/call/bind 时

  • this 指向绑定的;
function getColor(color) {
  this.color = color;
  console.log(this); // this 指的是实例 car
}

function Car(name, color) {
  console.log(this); // this 指的是实例 car
  this.name = name;
  getColor.call(this, color); // 使用 call后,getColor 方法中的 this 指向 car
}

var car = new Car('卡车', '绿色');
console.log(car.name, car.color);

/* 
  输出:卡车 绿色
*/
面试题:手写 call、apply、bind
  • 实现 bind
/**
 * 在不考虑 new 的优先级的情况下:
 */
Function.prototype.bind =
  Function.prototype.bind ||
  function (context) {
    const fn = this;
    // get bind 's params
    const args = Array.prototype.slice.call(arguments, 1);
    return function (...innerArgs) {
      const allArgs = [...args, ...innerArgs];
      return fn.apply(context, allArgs);
    };
  };

/**
 * 如果考虑到 new 的一个优先级
 */
// bind 返回的函数如果作为构造函数,搭配 new 关键字出现的话,这种绑定,就需要被忽略,this 要绑定在实例上,也就是说,new 操作符要高于bind 绑定
Function.prototype.bind =
  Function.prototype.bind ||
  function (context) {
    const fn = this;
    // get bind 's params
    const args = Array.prototype.slice.call(arguments, 1);

    var F = function () {};
    F.prototype = this.prototype;
    var bound = function () {
      var innerArgs = Array.prototype.slice.call(arguments);
      const allArgs = [...args, ...innerArgs];
      // 如果存在 new, 我绑定的对象不一样了
      return fn.apply(this instanceof F ? this : context || this, allArgs);
    };
    bound.prototype = new F();
    return bound;
  };

/**
 * 实现一个标准的 es5-shim 版 bind
 */
function bind(that) {
  var target = this;
  if (!isCallable(target)) {
    throw new TypeError('Function.prototype.bind called on incompatible ' + target);
  }

  var args = array_slice.call(arguments, 1);
  var bound;
  var binder = function () {
    if (this instanceof bound) {
      var result = target.apply(this, array_concat.call(args, array_slice.call(arguments)));
      if ($Object(result) === result) {
        return result;
      }
      return this;
    } else {
      return target.apply(that, array_concat.call(args, array_slice.call(arguments)));
    }
  };
  var boundLength = max(0, target.length - args.length);
  var boundArgs = [];
  for (var i = 0; i < boundLength; i++) {
    array_push.call(boundArgs, '$' + i);
  }
  bound = Function(
    'binder',
    'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }'
  )(binder);

  if (target.prototype) {
    Empty.prototype = target.prototype;
    bound.prototype = new Empty();
    Empty.prototype = null;
  }

  return bound;
}
  • 实现 call
function called(context) {
  const args = Array.prototype.slice.call(arguments, 1);
  // 用显式调用的方式,进行模拟
  context.fn = this;
  if (context) {
    const result = context.fn(...args);
    delete context.fn;
    return result;
  } else {
    return this(...args);
  }
}

场景 5:箭头函数时

  • 【定义时】离我最近的非箭头函数的上下文是啥,this 就是啥;
// 复习⼀下场景1
var name = 'tom';
var a = {
  name: 'jack',
  myfunc: function () {
    setTimeout(function () {
      console.log(this); // this 是 window
      console.log(this.name); // tom
    }, 0);
  },
};

a.myfunc();

// 稍微改变⼀下
var name = 'tom';
var b = {
  name: 'jack',
  myfunc: function () {
    var that = this;

    setTimeout(function () {
      console.log(this); // Window
      console.log(that); // this 是 b
      console.log(that.name); // jack
    }, 0);
  },
};

b.myfunc();

// 箭头函数
var name = 'tom';
var c = {
  name: 'jack',
  myfunc: function () {
    setTimeout(() => {
      console.log(this); // this 是 c
      console.log(this.name); // jack
    }, 0);
  },
};

c.myfunc();

// 箭头函数
var name = 'tom';
var d = {
  name: 'jack',
  myfunc: () => {
    setTimeout(() => {
      console.log(this); // this 是 Window
      console.log(this.name); // tom
    }, 0);
  },
};

d.myfunc();
function buybuybuyOuter() {
  console.log('this-outer =========', this); // wife

  const buybuybuy = () => {
    console.log('this ======== ', this); // wife
  };

  buybuybuy();
}

var wife = {
  name: 'wife',
};

wife.buybuybuyOuter = buybuybuyOuter;
wife.buybuybuyOuter();
function buybuybuyOuter() {
  console.log('this-outer =========', this); // wife

  const buybuybuy = () => {
    console.log('this ======== ', this); // wife
  };

  function innerCall() {
    console.log('innerCall ======== ', this); // window
    buybuybuy();
  }

  innerCall();
}

var wife = {
  name: 'wife',
};

wife.buybuybuyOuter = buybuybuyOuter;
wife.buybuybuyOuter();
var wife = {
  name: 'wife',
};

const buybuybuy = () => {
  console.log('this ======== ', this); // window
};

// call 和 apply 可以执行,但是无效
buybuybuy.call(wife);

总结⼀下:

image-20210512023734182.png

  1. 对于直接调用的函数来说,不管函数被放在什么了地方,this 都是 window
  2. 对于被别人调用的函数来说,被谁点出来的,this 就是谁;
  3. 在构造函数中,类中(函数体中)出现的 this.xxx = xxx; 中的 this 是当前类的一个实例;
  4. call/apply 时,this 是第一个参数;bind 要优先于 call/applycall 参数多,apply 参数少;
  5. 箭头函数没有自己的 this,需要看其外层的是否有函数,如果有,外层函数的 this 就是内部箭头函数的 this,如果没有,则 thiswindow

相关面试题

考察 this 三板斧

function show1() {
  console.log('this1 >>> ', this); // obj1
}

var obj1 = {
  show1: show1,
};

obj1.show1();

function show2() {
  console.log('this2 >>> ', this); // Window
}

var obj2 = {
  show2: function () {
    console.log('this3 >>> ', this); // obj2
    show2();
  },
};

obj2.show2();
var obj = {
  myName: 'jack',
  show: function () {
    console.log('this >>> ', this);
    console.log(this.myName);
  },
};

var func = obj.show;
func(); // this 指向 Window,this.myName 输出 undefined
obj.show(); // this 指向 obj,this.myName 输出 jack

// 逗号表达式
/* 
  (0, obj.show)();

  ==> 等同于
  var func = obj.show;
  func();
*/
var name = 'tom';

var obj = {
  name: 'obj',
  sub: {
    name: 'sub',
    show: function () {
      console.log('this >>> ', this);
      console.log(this.name); // sub
    },
  },
};

obj.sub.show(); // this 指向 sub
var name = 'tom';

var obj = {
  name: 'obj',
  sub: {
    name: 'sub',
    show: () => {
      console.log('this >>> ', this);
      console.log(this.name); // tom
    },
  },
};

obj.sub.show(); // this 指向 Window
// addEventListener 中的 this
let Person = {
  test: function () {
    console.log(this);
  },
};

let func = Person.test;

let app = document.getElementsByClassName('app');

app[0].addEventListener('scroll', Person.test); // this 指向监听的 div

window.addEventListener('scroll', Person.test); // this 指向 window

document.addEventListener('scroll', Person.test); // this 指向 document

app[0].addEventListener('scroll', function () {
  console.log('this ===== ', this); // this 指向监听的 div
  func(); // this 指向 window
});

app[0].addEventListener('scroll', () => {
  console.log('this ===== ', this); // this 指向监听的 window
  func(); // this 指向 window
});

作用域

// 提升之前
var person = 1;

function showPerson() {
  console.log(person); // ƒ person() {}
  var person = 2;
  function person() {}
  console.log(person); // 2
}

showPerson();

// 提升之后
var person = 1;

function showPerson() {
  function person() {}
  var person;
  console.log(person); // ƒ person() {}
  person = 2;
  console.log(person); // 2
}

showPerson();
// 提升之前
var person = 1;

function showPerson() {
  console.log(person); // ƒ person() {}
  function person() {}
  var person = 2;
  console.log(person); // 2
}

showPerson();

// 提升之后
var person = 1;

function showPerson() {
  function person() {}
  var person;
  console.log(person); // ƒ person() {}
  person = 2;
  console.log(person); // 2
}

showPerson();
var person = 1;

function showPerson() {
  console.log(person); // ƒ person() {}
  var person = 2;
  function person() {}
}

showPerson();
var person = 1;

function showPerson() {
  console.log(person); // ƒ person() {}
  function person() {}
  var person = 2;
}

showPerson();
for (var i = 0; i < 10; i++) {
  console.log(i);
}
/* 
  输出:0 ~ 9
*/

for (var i = 0; i < 10; i++) {
  setTimeout(function () {
    console.log(i);
  }, 0);
}
/* 
  输出:10 个 10
*/

for (var i = 0; i < 10; i++) {
  (function (i) {
    setTimeout(function () {
      console.log(i);
    }, 0);
  })(i);
}
/* 
  输出:0 ~ 9
*/

for (let i = 0; i < 10; i++) {
  console.log(i);
}
/* 
  输出:0 ~ 9
*/

补充:函数和变量的提升顺序!!!

函数提升要比变量提升的优先级要高一些,且不会被变量声明覆盖,但是会被变量赋值之后覆盖。

/* 
  提升之前
*/
console.log(a); // f a() {console.log(10)}
console.log(a()); // undefined

var a = 3;
function a() {
  console.log(10); // 10
}

console.log(a); // 3
a = 6;
console.log(a()); // Uncaught TypeError: a is not a function

/* 
  提升之后
*/
function a() {
  console.log(10);
}
var a;
console.log(a); // 变量提升后,a 的值不会被覆盖掉,所以输出:f a() {console.log(10)}
console.log(a()); // 10 undefined

a = 3;
console.log(a); // 重新赋值后,变量被覆盖,所以输出:3
a = 6;
console.log(a()); // Uncaught TypeError: a is not a function

/* 
	输出结果:
  f a() {console.log(10)}
  10
  undefined
  3
  Uncaught TypeError: a is not a function
*/

面向对象

function Person() {
  this.name = 1;
  return {}; // 返回一个对象,new 出来的实例指向此对象
}

var person = new Person();
console.log(person); // {}
console.log('name >>> ', person.name); // undefined
function Person() {
  this.name = 'jack';
}

Person.prototype = {
  show: function () {
    console.log(this); // 实例 Person
    console.log('name is >>> ', this.name); // name is >>>  jack
  },
};

var person = new Person();
person.show();
function Person() {
  this.name = 'jack';
}

Person.prototype = {
  name: 'tom',
  show: function () {
    console.log('name is >>> ', this.name);
  },
};

var person = new Person();

Person.prototype.show = function () {
  console.log('new show');
};

person.show(); // new show
function Person() {
  this.name = 'jack';
}

Person.prototype = {
  name: 'tom',
  show: function () {
    console.log('name is >>> ', this.name);
  },
};

var person = new Person();
var person2 = new Person();

person.show = function () {
  console.log('new show');
};

person2.show(); // name is >>>  jack
person.show(); // new show

综合题目

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

Person.prototype = {
  name: 'tom',
  show: function () {
    console.log('name is >>> ', this.name);
  },
};

Person.prototype.show(); // name is >>>  tom
new Person().show(); // name is >>>  jack

扩展

学习方法:费曼学习法

费曼学习法.jpg

手写代码

手写代码:juejin.cn/post/696871…