`new` 绑定:构造函数调用时 `this` 的指向

好的,朋友们,各位未来的代码艺术家们!今天咱们要聊聊 JavaScript 里一个既神秘又至关重要的概念——new 绑定。别害怕,这玩意儿听起来高大上,实际上就像你早上起来煎鸡蛋一样,只要掌握了诀窍,就能煎出完美的太阳蛋🍳!

开场白:this 的江湖地位

在 JavaScript 的世界里,this 可谓是“身价百倍”的大明星,走到哪里都被人追捧。它就像一个神秘的信使,总是带着当前执行环境的信息。但这家伙又很任性,它的指向并非一成不变,而是随着调用方式的不同而变化。搞清楚 this 的指向,是成为 JavaScript 高手的必经之路,否则你的代码就会像喝醉了酒的企鹅🐧,摇摇晃晃,让人摸不着头脑。

第一幕:new 绑定,闪亮登场!

今天咱们的主角 new 绑定,就是改变 this 指向的一种方式。当我们在 JavaScript 中使用 new 关键字调用一个函数时,事情就变得有趣起来了。这个函数不再只是一个普通的函数,而是摇身一变,成了一个“构造函数”。构造函数,顾名思义,就是用来构造对象的函数。

想象一下,你是一位建筑师,构造函数就是你的蓝图,而 new 关键字就像你的施工队,负责按照蓝图建造一栋房子(对象)。

第二幕:new 绑定的四大步骤,抽丝剥茧

new 绑定到底做了什么呢?别急,咱们一步一步来,就像剥洋葱一样,一层一层地揭开它的神秘面纱。

  1. 创建一个全新的对象。

    就像建筑师拿到一块空地,首先要做的就是打地基。new 关键字会默默地创建一个全新的、空的对象。这个对象就像一张白纸,等待着构造函数来填充内容。

  2. 将这个新对象的 [[Prototype]] 原型链指向构造函数的 prototype 属性。

    这步比较抽象,咱们打个比方。假设构造函数是一个模具,prototype 属性就是模具上的花纹。新创建的对象,会继承这个模具上的花纹,也就是说,它可以访问构造函数 prototype 属性上的属性和方法。这是一种原型继承的方式,是 JavaScript 中非常重要的概念。

    更通俗一点,你可以把 prototype 看作是祖传秘方,而新对象就是继承了这份秘方的后代,可以做出和祖先一样美味的菜肴🍜。

  3. 将构造函数中的 this 绑定到这个新对象。

    这是最关键的一步!new 关键字会把构造函数内部的 this 指向刚刚创建的新对象。这意味着,在构造函数内部,你可以通过 this 来操作这个新对象,给它添加属性、方法等等。

    这就好比建筑师拿着图纸,开始在新地基上建造房子。this 就成了建筑师手中的工具,可以用来搭建墙壁、安装门窗等等。

  4. 如果构造函数没有显式返回一个对象,则返回这个新对象;否则,返回构造函数显式返回的对象。

    最后一步,检查构造函数是否返回了值。如果构造函数没有使用 return 语句显式返回一个对象,那么 new 关键字会默默地把刚刚创建的新对象返回给你。但如果构造函数显式返回了一个对象,那么 new 关键字就会忽略之前创建的新对象,直接返回构造函数返回的对象。

    这就像建筑师建造完房子后,会检查一下是否符合图纸的要求。如果没有问题,就把房子交付给你;如果建筑师觉得房子还需要修改,或者想要用其他材料建造,那么他就可以直接交付你一个不同的房子。

第三幕:代码实战,手到擒来

理论讲了一大堆,不如撸起袖子,敲几行代码来得实在。咱们来看一个例子:

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.greet = function() {
    console.log(`你好,我叫 ${this.name},今年 ${this.age} 岁。`);
  };
}

const person1 = new Person('张三', 30);
const person2 = new Person('李四', 25);

person1.greet(); // 输出:你好,我叫 张三,今年 30 岁。
person2.greet(); // 输出:你好,我叫 李四,今年 25 岁。

在这个例子中,Person 是一个构造函数。当我们使用 new Person('张三', 30) 创建对象时,new 关键字做了以下几件事:

  1. 创建了一个空对象 {}
  2. 将这个空对象的 [[Prototype]] 指向 Person.prototype
  3. Person 函数中的 this 绑定到这个空对象。
  4. 执行 Person 函数,给这个新对象添加了 nameagegreet 属性。
  5. 返回这个新对象。

最终,person1person2 就成了 Person 对象的实例,它们拥有各自的 nameage,并且可以调用 greet 方法。

第四幕:优先级之争,谁是老大?

this 的指向并非只有 new 绑定一种方式,还有其他几种绑定方式,比如隐式绑定、显式绑定和默认绑定。当多种绑定方式同时存在时,到底谁说了算呢?这就涉及到绑定规则的优先级问题。

优先级从高到低依次是:

  1. new 绑定:当使用 new 关键字调用函数时,this 总是指向新创建的对象。
  2. 显式绑定:使用 callapplybind 方法显式地指定 this 的指向。
  3. 隐式绑定:当函数作为对象的方法调用时,this 指向该对象。
  4. 默认绑定:在非严格模式下,this 指向全局对象(浏览器中是 window,Node.js 中是 global);在严格模式下,this 指向 undefined

为了更清晰地展示优先级,咱们用一个表格来总结一下:

绑定方式 优先级 this 指向 示例
new 绑定 最高 新创建的对象 const obj = new MyClass();
显式绑定 次高 callapplybind 指定的对象 myFunc.call(obj);myFunc.apply(obj);const boundFunc = myFunc.bind(obj); boundFunc();
隐式绑定 较低 调用该方法的对象 obj.myFunc();
默认绑定 最低 非严格模式:全局对象(windowglobal
严格模式:undefined
myFunc();(在全局作用域中调用)

第五幕:避坑指南,防患未然

掌握了 new 绑定,并不意味着可以高枕无忧了。在实际开发中,还是有一些需要注意的地方,避免掉入陷阱。

  • 忘记使用 new 关键字:如果你忘记使用 new 关键字调用构造函数,那么 this 就会指向全局对象,导致意想不到的错误。为了避免这种情况,可以养成一个良好的习惯:构造函数的首字母大写,以提醒自己这是一个构造函数,需要使用 new 关键字调用。
  • 构造函数显式返回对象:如果构造函数显式返回了一个对象,那么 new 关键字会忽略之前创建的新对象,直接返回构造函数返回的对象。这可能会导致你创建的对象不是你想要的。
  • 箭头函数不能作为构造函数:箭头函数没有自己的 this,它会继承外层作用域的 this。因此,箭头函数不能作为构造函数使用。如果你尝试使用 new 关键字调用箭头函数,会抛出一个错误。

第六幕:进阶之路,更上一层楼

如果你想更深入地了解 new 绑定,可以研究以下几个方面:

  • Object.create() 方法:这个方法可以创建一个新对象,并将指定的对象作为新对象的原型。这是一种更加灵活的原型继承方式。
  • instanceof 运算符:这个运算符可以判断一个对象是否是某个构造函数的实例。
  • ES6 的 class 语法:ES6 引入了 class 语法,使得 JavaScript 的面向对象编程更加简洁和易懂。class 语法本质上还是基于原型继承的,只不过它提供了一种更加友好的语法糖。

第七幕:举一反三,融会贯通

掌握了 new 绑定,可以帮助你更好地理解 JavaScript 的面向对象编程。你可以尝试用 new 绑定来创建各种各样的对象,比如用户、产品、订单等等。你还可以结合其他绑定方式,灵活地控制 this 的指向,编写出更加强大和灵活的代码。

尾声:代码的艺术,永无止境

new 绑定只是 JavaScript 众多知识点中的一个。学习编程就像攀登一座高山,需要不断地学习、实践和总结。希望今天的分享能够帮助你更上一层楼,早日成为一名真正的代码艺术家👨‍🎨!

最后,送给大家一句我最喜欢的代码格言:

Code is poetry. Write beautifully.

祝大家编程愉快,Bug 永远远离!🎉

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注