javascript中的new原理及实现

news2024/11/26 0:33:30

在js中,我们通过new运算符来创建一个对象,它是一个高频的操作。我们一般只是去用它,而很少关注它是如何实现的,它的工作机制是什么。

1 简介

本文介绍new的功能,用法,补充介绍了不加new也同样创建对象的方式,分析了new的原理,最后模拟了new的实现。

学习本文内容需要你了解js中对象,原型链,call,bind,arguments的用法。


2.new 的基本用法

我们通过new来创建对象,它的基本格式是:

var 对象 = new 函数([参数])

这里的函数可以是内置构造器,也可以是用户自己定义的函数。

例如:

var arr = new Array ();

此时,arr将可以使用Array.prototype上的全部方法。

更一般的情况,我们会使用自己定义的构造器。

function F(name,age){    this.name = name;    this.age = age}F.prototype.hello = function(){    console.log(this.name,this.age)}
var f1 = new F('curry', 30);console.log(f1)f1.hello()

对如上的代码有几点说明如下:

函数F 在被调用的过程中,在前面加 new ,所以这个函数是被当作构造器来使用了。

f1之所以可以调用 hello方法,也是因为原型链的缘故。


3 构造器的返回值

一般来讲,如果你要把一个函数当做构造函数来使用,在这个函数的内部是不应该去设置返回值的。但是,如果它设置了返回值呢?

先说出答案如下:

return后面跟着不是对象,就会不管return语句,返回this对象;

return后面跟着一个对象,new会返回return语句指定的对象;

先来看构造器中,return后面跟着不是对象的情况。

var Vehicle = function () {  this.price = 1000;  return 1000;};
(new Vehicle()) === 1000// false

上面代码中,构造函数Vehicle的return语句返回一个数值。这时,new命令就会忽略这个return语句,就当它不存在,还是正常返回“构造”后的this对象。

但是,如果return语句返回的是一个对象,new命令会返回这个新对象,而不再是this对象,这一点需要特别引起注意。

var Vehicle = function (){  this.price = 1000;  return { price: 2000 };};
(new Vehicle()).price// 2000

上面代码中,构造函数Vehicle的return语句,返回的是一个新对象。new命令会返回这个对象,而不是this对象。

4 不加new也能创建对象吗?

对上面的代码,我们稍微改一下,在使用构造器时,故意去掉new这个关键字。如下:

function F(name,age){    this.name = name;    this.age = age}
var f1 = F('curry', 30); // 不加new console.log(f1)

此时,我们把F当作一个普通的函数来调用,由于在函数F内部并没有明确写出return语句,所以f1的值是undefined。同时上面的代码还会有另一个隐藏的后果:在执行F时,由于this的值是指向window,所以上面的代码还会给window对象添加两个属性。如下:

图片

那么问题来了,如何确保这个F只能被用作构造器,而不能当作普通函数来用呢?

两种解决思路:

如果不加new就报错。

如果不加new就偷着给你加上。


4.1 构造函数内部使用严格模式

为了保证构造函数必须与new命令一起使用,一个解决办法是,构造函数内部使用严格模式,即第一行加上use strict。这样的话,一旦忘了使用new命令,直接调用构造函数就会报错。

function F(name,age){     'use strict'; // 这句新加的    this.name = name;    this.age = age}
var f1 = F('curry', 30); // 不加newconsole.log(f1)

上面的代码会报错,错误是Uncaught TypeError: Cannot set property 'name' of undefined 。因为在函数内部开启了严格模式之后,函数内部的this将不会默认指向window,它的值会是undefined。

一旦代码报错了,相当于提醒你必须给加上new,你就自己给它加上吧。

4.2 自动加上new

还可以在构造函数内部判断,当前调用是否使用new命令,如果发现没有使用new,则直接返回一个实例对象。

function F(name,age){   // 如果没有用new,this就不会是F的实例  if (!(this instanceof F)) {    return new F(name,age);  }  this.name = name;  this.age = age}
var f1 = new F('a','30');var f2 = F('b','30');console.log(f2)

上面代码中的构造函数,不管加不加new命令,都会得到同样的结果。如下:

图片


5 new 原理

使用new命令时,在构造函数内部依次执行下面的步骤。

第一步:创建一个空对象,作为将要返回的对象。

第二步:将这个空对象的原型指向构造函数的prototype属性。这一步的作用是让这个对象能沿着原型链去使用构造函数中prototype上的方法。

第三步:将这个空对象赋值给构造函数内部的this关键字,执行构造函数。这一步的作用是让构造器中设置在this上的属性最终设置在这个对象上。

第四步:返回这个对象。

以如下代码为例:

function F(name,age){   this.name = name;  this.age = age}F.prototype.hello = function(){    console.log(this.name,this.age)}var f = new F('a','30');

则上面四步的伪代码如下:

第一步:var obj = {}

第二步:obj.__proto__ = F.prototype

第三步:F.apply(obj,参数)

第四步:return obj

下面模拟一下new的实现。由于new是一个关键字,我们写一个单独的函数_new来模拟,最终的目标是:

function F(name,age){   this.name = name;  this.age = age}
F.prototype.hello = function(){    console.log(this.name,this.age)}// 使用模拟new,var f1 = _new(F,'a',30);//  希望达到与new F('a',30)一致的效果f1.hello();

你可以先想一想, 如何实现_new哈。

下面是一个参考实现:

function _new(constructor, ...args) {
  // 创建一个新对象,将其原型设置为构造函数的原型
  const newObj = Object.create(constructor.prototype);

  // 调用构造函数,并将新对象作为上下文
  const result = constructor.apply(newObj, args);

  // 如果构造函数有显式返回一个对象,则返回该对象;否则,返回新对象
  return typeof result === 'object' && result !== null ? result : newObj;
}

// 测试
function F(name, age) {
  this.name = name;
  this.age = age;
}

F.prototype.hello = function() {
  console.log(this.name, this.age);
}

var f1 = _new(F, 'a', 30);
f1.hello(); // 输出:a 30

代码解释:
 

// 创建一个新对象,将其原型设置为构造函数的原型
  const newObj = Object.create(constructor.prototype);

//  等同于:
    const newObj = {}; // 创建一个空对象

    newObj.__proto__ = constructor.prototype; // 将新对象的原型设置为构造函数的原型



 // 调用构造函数,并将新对象作为上下文
  const result = constructor.apply(newObj, args);

解释:

当我们调用 `constructor.apply(newObj, args)` 时,我们将 `constructor` 构造函数作为函数调用,并将 `newObj` 对象作为其上下文。

关于 `apply` 方法,它是 JavaScript 中用于调用函数的方法,可以设置函数调用时的上下文对象以及参数列表。它接收两个参数:上下文对象和参数数组。

在这行代码中,我们将 `newObj` 对象作为上下文对象,这样在构造函数内部可以通过 `this` 来引用这个新创建的对象。然后,我们将 `args` 数组展开作为参数列表传递给构造函数。

这样,构造函数就会在 `newObj` 对象的上下文中执行,从而将构造函数内部的属性和方法赋值给 `newObj` 对象。

最后,我们的实现返回了这个新创建的对象,以便我们可以像使用 `new` 关键字一样访问该对象的属性和方法。

版权信息:凡人进阶。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1179284.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HDD最后的冲刺:大容量硬盘的奋力一搏

1.引言 在上一篇文章(微软Azure云数据中心工作负载分享:SSD与HDD,何去何从?)中,我们提到在应对SSD QLC/PLC大容量的挑战中,HDD也是在不断的努力,推出HAMR,SMR等新介质。…

【已解决】ModuleNotFoundError: No module named ‘dgl‘

禁止使用下面方法安装DGL,这种方法会更新你的pytorch版本,环境越变越乱 pip install dgl 二是进入DGL官网:Deep Graph Library (dgl.ai),了解自己的配置情况,比如我cuda11.8,ubuntu,当然和linux是一样的 …

卡尔曼滤波之二:Python实现

卡尔曼滤波之二:Python实现 1.背景描述2.构建卡尔曼滤波公式2.1 预测2.2 更新 3.代码实现3.1 输入值3.2 pykalman包实现3.3 不使用Python包实现3.4 效果可视化 参考文献 了解了卡尔曼滤波之一:基本概念,可以结合代码来理解下卡尔曼滤波的2个预…

STM8单片机在医疗设备中的应用和优势

STM8单片机作为一种高性能、低功耗的微控制器,在医疗设备领域得到了广泛的应用。本文对STM8单片机在医疗设备中的应用进行了研究,探讨了它在医疗设备中的优势和特点,并分析了其在提升医疗设备性能、精确控制和数据处理等方面的应用效果。 一…

接口幂等性详解

1. 什么是幂等性 幂等性指的是对同一个操作的多次执行所产生的影响与一次执行的影响相同。无论操作执行多少次,系统状态都应该保持一致。 在计算机科学和网络领域中,幂等性通常用来描述服务或操作的特性。对于RESTful API或HTTP方法,一个幂…

【Linux】服务器与磁盘补充知识,硬raid操作指南

服务器硬件 cpu 主板 内存 硬盘 网卡 电源 raid卡 风扇 远程管理卡 1.硬盘尺寸: 目前生产环境中主流的两种类型硬盘 3.5寸 和2.5寸硬盘 2.5寸硬盘可以通过使用硬盘托架后适用于3.5寸硬盘的服务器 但是3.5寸没法转换成2.5寸 2.如何在服务器上制作raid 华为服务器为例子做…

DAY46 139.单词拆分 + 多重背包 + 背包问题总结篇

139.单词拆分 题目要求:给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明: 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 思路 完全背…

NVMe FDP会被广泛使用吗?

文章开头,我们需要先了解固态硬盘的读写机制。我们知道,固态硬盘的存储单元是由闪存颗粒组成的,无法实现物理性的数据覆盖,只能擦除然后写入,重复这一过程。因而,我们可以想象得到,在实际读写过…

04-SpringBoot的基础配置及其配置文件分类,解决Yaml文件失效问题

SpringBoot的配置 SpringBoot是用来提高Spring程序的开发效率的,使用SpringBoot后几乎不用做任何配置功能就有了,因为很多功能已经有默认配置帮我们做好了 配置文件的相关配置 在一个项目中不同的技术对应不同的配置文件并且这些配置文件的格式也不统一 SpringBoot提供了一…

打印由*组成的菱形

如图所示,这是我们要用代码所实现的图形。 那么我们该如何实现这个呢,对于这种题,我们只有静下心来找其中的规律了。 我们先来看看它的上面部分: 它是由空格和星号组成的,那么我们是不是可以先打印空格然后再打印星号…

2023 年 API 安全状况

在当今快速变革的数字世界中,API 已成为快速交付业务功能的关键。这些数字连接器支撑着我们今天见证的大部分企业创新,从无缝的客户体验到集成的合作伙伴生态系统。 随着 API 使用量的激增,潜在风险呈指数级增长。让我们用硬数据来说明 API …

【redis 面试题】③ 缓存雪崩

文章目录 前言一、什么是缓存雪崩二、缓存雪崩的解决方案 前言 跟着B站的黑马程序员学习面试题,目前是redis的第三个内容——缓存雪崩 课程传送门:redis——缓存雪崩 一、什么是缓存雪崩 缓存雪崩是设置缓存时采用了相同的过期时间,导致缓存…

Pytorch 快速参数权重初始化

定义一个函数: 这里比如要初始化2维卷积权重值,采用xaiver 数据分布,还有很多其他的数据分布可以探索 def weights_init(m):if isinstance(m, nn.Conv2d):xavier(m.weight.data)xavier(m.bias.data) 然后定义一个含2维卷积的网络&#xff…

HTML5+CSS3小实例:带功能区的图片悬停特效

实例:带功能区的图片悬停特效 技术栈:HTML+CSS 效果: 源码: 【HTML】 <!DOCTYPE html> <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><meta name="viewport" content=&…

esxi 6.7下安装黑裙

esxi上创建一个黑裙系统的虚拟机&#xff0c;用来存资料 一、工具 硬件&#xff1a; 工控机&#xff1a;装有esxi6.7系统&#xff08;192.168.100.2&#xff09;&#xff0c;配置&#xff1a;3865U&#xff0c;16G内存&#xff0c;120Gmsata120sata硬盘&#xff0c;6个网口 主…

oracle转换人大金仓全过程

前提 Oracle服务器&#xff1a;创建用户&#xff0c;导入数据库人大金仓服务&#xff1a;创建用户 注意&#xff1a;两者的参数设置要保持一致&#xff08;数字集UTF-8&#xff09;&#xff0c;人大金仓设置大小字符不敏感 人大金仓工具介绍 数字库开发管理工具&#xff1a;链…

【PTE-day03 报错注入】

报错注入 1、报错注入 group by count2、报错注入 extractvalue3、报错注入updatexml1、报错注入 group by count http://124.222.124.9:8888/Less-5/?id=-1 union select 1,count(*),concat((select database()),ceil(rand(0)*2)) as a from information_schema.tables grou…

思维模型 飞轮效应

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。万事开头难&#xff0c;坚持就不难。 1 飞轮效应的应用 1.1使用飞轮效应的亚马逊 亚马逊的创始人杰夫贝索斯&#xff08;Jeff Bezos&#xff09;提出了“飞轮理论”&#xff0c;即通过不断…

jira Licenses更新步骤

有时候我们不想花钱使用jira,那么只有通过一个月以续期的方式来使用jira。下面提供下自己实测的方式 1、获取License Key 登录地址&#xff1a;https://my.atlassian.com 登录自己的Google账号&#xff0c;进入到下面账号&#xff0c;然后点击“New Trial License” product上…

HTB——introduction to active directory

文章目录 一、Active directory structure二、Active Directory Terminology 一、Active directory structure Active Directory &#xff08;AD&#xff09; 是用于 Windows 网络环境的目录服务。它是一种分布式分层结构&#xff0c;允许集中管理组织的资源&#xff0c;包括用…