JavaScript原理篇——理解对象、构造函数、原型、继承

对象:在JavaScript中,几乎所有的东西都是对象,包括基本数据类型的包装对象。对象是属性的集合,每个属性都有一个键和一个值。对象可以通过字面量、构造函数或Object.create()等方式创建。

构造函数:构造函数是用来创建对象的函数,通过new关键字调用构造函数可以创建对象实例。构造函数可以定义对象的属性和方法,实例化后的对象可以共享构造函数中定义的方法。

原型:每个JavaScript对象都有一个原型对象,可以通过__proto__访问。原型是对象的模板,包含对象共享的属性和方法。构造函数的prototype属性指向原型对象,实例的__proto__属性指向构造函数的原型对象。

继承:JavaScript使用原型链实现继承,子类对象可以继承父类对象的属性和方法。子类构造函数的原型对象可以设置为父类构造函数的实例,从而实现继承。继承可以通过原型链继承、构造函数继承、组合继承等方式实现。

对象基础操作

javaScript 对象(Object)是 JavaScript 中最基本的数据结构之一,有许多基础操作可以用来创建、操作和管理对象。以下是一些常见的 JavaScript 对象基础操作:

创建对象

对象字面量表示法:

字面量是创建对象最常用的表示方法

const obj = {
  property1: value1,
  property2: value2,
  // ...
};

使用 Object 构造函数:

const obj = new Object();
obj.property1 = value1;
obj.property2 = value2;

使用 Object.create() 方法:

const obj = Object.create(Object.prototype, {
  property1: {
    value: value1,
    writable: true,
    configurable: true,
    enumerable: true,
  },
  property2: {
    value: value2,
    writable: true,
    configurable: true,
    enumerable: true,
  },
});

访问对象属性

点表示法:

const value1 = obj.property1;

方括号表示法:

const value1 = obj['property1'];

添加/修改/删除对象属性

添加或修改对象属性:

obj.property1 = value1;
// 或
obj['property1'] = value1;

删除对象属性:

delete obj.property1;
// 或
delete obj['property1'];

检查对象属性

检查对象是否有某个属性:

if ('property1' in obj) {
  // 对象 obj 有 property1 属性
}

检查对象自有属性:

if (Object.prototype.hasOwnProperty.call(obj, 'property1')) {
  // 对象 obj 自有 property1 属性
}

枚举对象属性

使用 for...in 循环:

for (const property in obj) {
  if (obj.hasOwnProperty(property)) {
    console.log(property, obj[property]);
  }
}

获取对象原型

使用 Object.getPrototypeOf()

const prototype = Object.getPrototypeOf(obj);

继承和扩展对象

使用 Object.create() 创建继承对象:

const parentObj = {
  property1: value1,
};
const obj = Object.create(parentObj);

使用 Object.assign() 合并对象:

const obj1 = {
  property1: value1,
};
const obj2 = {
  property2: value2,
};
const obj = Object.assign({}, obj1, obj2);

使用 Object.setPrototypeOf() 设置原型:

const prototypeObj = {
  // ...
};
Object.setPrototypeOf(obj, prototypeObj);

对象key要求

在JavaScript中,对象的键(key)有一些基本的要求:

  1. 唯一性:每个键必须是唯一的,不能有两个相同的键存在于同一个对象中。如果有重复的键,后面的值会覆盖前面的值。

  2. 数据类型:键可以是以下几种数据类型:

    • 字符串:字符串是JavaScript中最常见的键类型,例如 {"name": "John"}
    • 符号(Symbol):从ES6开始,可以使用Symbol创建独一无二的键,例如 let obj = { [Symbol('key')]: 'value' }
    • 基本数据类型:其他基本数据类型(如数字、布尔值)会自动转换为字符串,例如 let obj = { 1: 'one', true: 'yes' },但不推荐,因为可能会与预期不符。
  3. 可枚举性:默认情况下,对象的键是可枚举的,这意味着它们在for...in循环中会被遍历到。可以通过Object.defineProperty来改变键的可枚举性。

  4. 保留字:JavaScript有一些保留字不能用作对象的键,如 class, for, function 等,如果尝试用这些词作为键,会引发错误。

  5. 空值nullundefined 也不能作为键,因为它们在JavaScript中被视为特殊的值。

基本类型的包装对象Number/Boolean/String

包装对象是 JavaScript 中的三个构造函数:Number、String 和 Boolean。这些构造函数可以用于创建对应的包装对象:

  • new Number(value) 创建一个 Number 对象,包装一个数值值。
  • new String(value) 创建一个 String 对象,包装一个字符串值。
  • new Boolean(value) 创建一个 Boolean 对象,包装一个布尔值。

在JavaScript中,基本数据类型(如字符串、数字、布尔值等)并不是对象,它们是不可变的。然而,为了能够调用一些方法和属性,JavaScript提供了基本数据类型的包装对象。这些包装对象是临时创建的对象,用于给基本数据类型值提供对象形式的方法和属性访问。

举个例子,当你使用"Hello".toUpperCase()时,JavaScript会临时将字符串"Hello"转换为一个字符串对象,然后调用toUpperCase()方法,最后丢弃这个临时对象。这样就可以在基本数据类型上调用对象的方法。

需要注意的是,虽然可以在基本数据类型上调用对象的方法,但是这些包装对象并不是基本数据类型的值本身,它们是对象。因此,在比较基本数据类型值时,最好使用严格相等运算符(===)来避免类型转换带来的意外结果。

构造函数、原型对象、实例

构造函数也是通过function方式定义的函数,内部使用this,通过new实例化后this的属性和方法执行实例本身。

原型是一个对象,原型对象依附于构造函数存在。构造函数通过.prototype访问原型对象,并通过function.prototype.方法(){}的形式添加实例所需的公共方法。

实例是通过构造函数通过new生成的,实例能够使用构造函数原型对象上所有公共的属性和方法。

通常代码逻辑是:写构造函数,为构造函数原型对象新增方法,通过new得到实例对象

构造函数VS普通函数

根据 JavaScript 的特性,函数在 JavaScript 中也是一种对象,被称为“可调用的对象”。函数对象和普通对象一样,可以拥有自己的属性和方法。在 JavaScript 中,函数可以被当作构造函数来使用,通过 new 关键字来实例化对象。当一个函数被用作构造函数时,会创建一个新的对象,该对象的原型会指向构造函数的原型对象(即构造函数的 prototype 属性)。

函数对象的 prototype 属性是一个指向原型对象的指针,它包含了一个对象的属性和方法,可以被实例化的对象所共享。通过在函数对象上设置原型对象的属性和方法,可以实现对所有实例对象的共享属性和方法。

虽然大部分函数可能不是专门设计用来作为构造函数的,但是在 JavaScript 中,任何函数都可以被当作构造函数来使用。这也是 JavaScript 中的一种灵活性和特性,使得开发者可以根据需要灵活地使用函数对象来实现不同的功能。

new关键字

new构造函数,返回一个对象,要么是构造函数本身有返回值,并且返回值是对象;要么是构造函数生成的实例对象。

构造函数没有返回值

function Person(name) {
  this.name = name;
}
const person = new Person("lucas");
console.log(person);

返回的是person实例对象

构造函数有返回值

function Person(name) {
  this.name = name;
  return { 1: 1 };
}
const person = new Person("lucas");
console.log(person);

构造函数如果有显式返回值,且返回值为对象类型,那么构造函数返回结果不再是目标实例。

构造函数返回的非对象类型

返回的是非对象,不影响实例的返回。

function Person(name) {
  this.name = name;
  return 12;
}
const person = new Person("lucas");
console.log(person);

手写new

思路:new的过程是创建一个空对象,继承构造函数的原型,在新的对象上执行构造函数,如果构造函数有返回值并且是对象类型,那么返回该对象,否则返回新对象

function myNew(constructor, ...args) {
  let newObj = Object.create(constructor.prototype);
  //执行构造函数,绑定this到newObj,接收构造函数的返回值
  let result = constructor.apply(newObj, args);
  return typeof result == "object" ? result : newObj;
}
function Person(name) {
  this.name = name;
  return { 1: 1 };
}

let person = myNew(Person, "lucas");
console.log(person);

实例、构造函数和原型对象三者关系

在浏览器中可以通过__proto__访问原型对象,但是在代码里建议使用es6提供的Object.getPrototypeOf(对象)方式获取原型对象。以下为了方便,使用__proto__

__proto__和prototype关系__proto__constructor对象独有的,原型也是对象,所以原型也有constructor构造函数和__proto__指针。prototype属性是函数独有的 

实例可以通过隐式原型__proto__访问构造函数的原型对象。构造函数又可以通过prototype属性访问到构造函数的原型对象。因此中间有这样一个相等关系:实例.__proto__==构造函数.prototype

实例可以通过constructor构造器访问自己的构造函数。这个特性通常也可用于判断对象的类型,但是对象的constructor是可以更改的,因此使用constructor也会不准确。默认情况:实例.constructor=构造函数

原型对象通过constructor指向构造函数,第三个等式:构造函数.prototype.constructor===构造函数

如果构造函数继承了父类的构造函数,同理,构造函数的原型对象通过隐式原型__proto__访问原型对象。等式:构造函数的原型对象.__proto__==父类构造函数的原型

注意是原型对象有__proto__而不是构造函数,构造函数本身只有prototype属性!

当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是 Object.prototype 所以这就是我们新建的对象为什么能够使用 toString() 等方法的原因。

测试题

测试题一:下面代码输出结果

function Foo() {
  getName = function () {
    console.log(1);
  };
  return this;
}
Foo.getName = function () {
  console.log(2);
};
Foo.prototype.getName = function () {
  console.log(3);
};
var getName = function () {
  console.log(4);
};
function getName() {
  console.log(5);
}

//请写出以下输出结果:
Foo.getName(); 
getName();
Foo().getName(); 
getName(); 
new Foo.getName(); 
new Foo().getName(); 
new new Foo().getName(); 

思路:考察构造函数,普通函数、var变量提升、 函数声明和函数表达式重名时的处理逻辑。

  • 任何函数都是普通对象,都可以添加自己的属性
  • 任何函数都可以当做构造函数被new调用,且任何函数都有原型对象prototype属性,只不过大部分函数不是标准的构造函数内容而已

执行Foo.getName()时,获取的是Foo的属性getName,输出2。属性通过key冒号的形式显示定义或者使用点.操作符,因此是2。

执行getName()方法时,执行的是全局的getName方法。观察代码,同时使用var声明了一个函数表达是和函数声明getName。由于函数声明大于变量的声明,因此函数声明提升,但是代码执行到赋值语句时,getName函数的函数体被重写了。因此函数表达式和函数声明重复时,函数表达式的函数体是最终要执行的函数。这里getName输出4

Foo().getName(),这个语句的意思是先执行Foo方法,由于Foo返回的是this,作为普通方法执行,this执行全局对象,此时getName仍然是全局的getName方法,但是观察Foo函数内部,getName方法又被重写了,所以输出的是1

执行getName方法,全局的getName方法,跟上面同步,也是1

new Foo.getName()将Foo.getName()作为构造函数执行,输出2

new Foo().getName()将Foo作为构造函数执行,返回的实例调用getName,因此访问的是Foo原型对象上的getName方法,输出3

最后一个new new Foo().getName(),仍然输出3

 测试题二:下面代码的输出结果

// example1
var a = {},b='123',c=123
a[b] = 'b'
a[c] = 'c'
console.log(a[b])

// example2
var a = {},b=Symbol('123'),c=Symbol('123')
a[b] = 'b'
a[c] = 'c'
console.log(a[b])

// example3
var a = {},b={key:'123'},c={key:'456'}
a[b] = 'b'
a[c] = 'c'
console.log(a[b])

思路:JS对象key的数据类型

  • 只能是字符串和symbol类型
  • 其他类型会被转化为字符串
  • 字符串会直接调用toString方法
// example1
var a = {},b='123',c=123
a[b] = 'b'
a[c] = 'c' // c是数字会被转为字符串a['123'] = 'c' 覆盖上一个'b'
console.log(a[b]) // c

// example2
var a = {},b=Symbol('123'),c=Symbol('123')
a[b] = 'b' // Symbol('123')是一个独一无二的值,每次都不一样。不会被覆盖
a[c] = 'c' 
console.log(a[b]) // b

// example3
var a = {},b={key:'123'},c={key:'456'}
a[b] = 'b' // 对象作为key被被转为'[object Object]'  a['[object Object]'] = 'b'
a[c] = 'c'
console.log(a[b]) // c

 答案:c    b   c

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

5月9(信息差)

🌍 可再生能源发电量首次占全球电力供应的三成 🎄马斯克脑机接口公司 Neuralink 计划将 Link 功能扩展至现实世界,实现控制机械臂、轮椅等 马斯克脑机接口公司 Neuralink 计划将 Link 功能扩展至现实世界,实现控制机械臂、轮椅等…

Python turtle绘制图形详解

Python 的 Turtle 模块是一个简单而直观的绘图工具,可以帮助初学者理解基本的图形绘制概念。 1.导入 Turtle 模块: import turtle 2.创建 Turtle 对象: t turtle.Turtle() 3.绘制图形: 4.移动Turtle对象:t.forward(di…

【PMP战报】2024.3.10 PMP考试成绩出炉

PMP成绩查询及电子版证书下载 https://mp.weixin.qq.com/s/HgWrZWjJ0cScEYs4w1b4iwPMP项目管理学习专栏https://blog.csdn.net/xmws_it/category_10954848.html?spm1001.2014.3001.5482 2024年3月中国大陆的认证考试已经顺利结束。 从2024年5月7日开始,大约一周内…

小程序如何注销

随着移动互联网的深入发展,管控也越来越严格。现在小程序都要求进行ICP备案,不管是新注册的还是以往注册的。很多商家的小程序本身处于无运营状态,现在要求备案,还不如直接注销。下面,将详细介绍小程序注销的步骤和注意…

报错(已解决):无法加载文件 D:\code\NodeJs\pnpm.ps1,因为在此系统上禁止运行脚本。

问题: 在vscode运行uniapp项目需要拉取全部依赖,需要使用到pnpm,在vscode终端运行命令:pnpm install后报错: 解决办法: 1:我未安装pnpm,首先打开电脑cmd,运行下列命令&a…

XXL-JOB定时任务

1. xxl-job初识 1.1 xxl-job介绍 xxl-job 是大众点评大佬徐雪里开源的一款分布式任务调度框架,具有简单易用、轻量级、可扩展的特点。相比于Spring Task, Quartz,xxl-job有记录执行日志和运行大盘,方便开发人员和运维人员更好的管理任务。 …

震惊,现在面试都加科技与狠货了

震惊,现在面试都加科技与狠货了 生成式AI盛行的现在,程序员找工作变容易了吗我和老痒喝着大酒,吃着他的高升宴,听他说他面试的各种细节,老狗我只恨自己动作慢了一步,不然现在在那侃侃而谈的就是我了。 面试…

基于springboot+jsp+Mysql的商务安全邮箱邮件收发

开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:…

使用QLoRA在自定义数据集上finetuning 大模型 LLAMA3 的数据比对分析

概述: 大型语言模型(LLM)展示了先进的功能和复杂的解决方案,使自然语言处理领域发生了革命性的变化。这些模型经过广泛的文本数据集训练,在文本生成、翻译、摘要和问答等任务中表现出色。尽管LLM具有强大的功能,但它可能并不总是与特定的任务或领域保持一致。 什么是LL…

探索全新商业模式:循环购的奥秘

你是否曾经遇到过这样的疑问:为何有的商家会推出“消费1000送2000”的优惠活动?每天还有钱可以领取,甚至还能提现?这背后究竟隐藏着怎样的商业逻辑?今天,作为你们的私域电商顾问,我将带大家深入…

【C++】继承 — 继承的引入、赋值切片详细讲解

前言 我们知道C语言是一门面向对象编程的语言,而面向对象编程有三大特性,它们分别是: 封装继承多态 目录 1. 继承的概念及定义1.1继承的概念1.2继承的定义格式1.3 继承的使用 2 基类和派生类对象赋值转换3 继承中的作用域3.1 派生类对象的存…

STM32使用L9110驱动电机自制小风扇

1.1 介绍: 该电机控制模块采用L9110电机控制芯片。该芯片具有两个TTL/CMOS兼容输入端子,并具有抗干扰特性:具有高电流驱动能力,两个输出端子可直接驱动直流电机,每个输出端口可提供750800mA动态电流,其峰值…

汽车行业芯片 车规级芯片 单车芯片( soc mcu)数量

链接:https://xueqiu.com/3000217281/272114755 10大车规级MCU芯片10大车规级MCU芯片 汽车芯片是什么? 汽车芯片即车规级芯片,标准要高于工业级和民用级芯片,仅次于军工级芯片。芯片大概有以下四种级别,分别是军工级…

Django关于ORM的增删改查

Django中使用orm进行数据库的管理,主要包括以下步骤 1、创建model, 2、进行迁移 3、在视图函数中使用 以下的内容可以先从查询开始看,这样更容易理解后面删除部分代码 主要包括几下几种: 1、增 1)实例例化model,代…

js逆向,参数加密js混淆

关键词 JS 混淆、源码乱码、参数动态加密 逆向目标 题目1:抓取所有(5页)机票的价格,并计算所有机票价格的平均值,填入答案。 目标网址:https://match.yuanrenxue.cn/match/1目标接口:https://ma…

buuctf-misc题目练习二

ningen 打开题目后是一张图片,放进winhex里面 发现PK,PK是压缩包ZIP 文件的文件头,下一步是想办法进行分离 Foremost可以依据文件内的文件头和文件尾对一个文件进行分离,或者识别当前的文件是什么文件。比如拓展名被删除、被附加…

Nacos Docker 快速部署----解决nacos鉴权漏洞问题

Nacos Docker 快速部署 1. 说明 1.1 官方文档 官方地址 https://nacos.io/zh-cn/docs/v2/quickstart/quick-start.html docker启动文件的gitlhub地址 https://github.com/nacos-group/nacos-docker.git 问题: 缺少部分必要配置与说明 1.2 部署最新版本Nacos&…

【Linux调试器】:gdb的使用(常见指令)

朋友们、伙计们,我们又见面了,本期来给大家解读一下有关Linux调试器gdb的使用,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! C 语 言 专 栏:C语言:从入门到精通…

数据结构与算法之树和二叉树--树和二叉树的一些性质

目录 前言 一、树的定义 二、树的若干术语 1.结点的度 2.叶子 3.双亲与孩子 4.兄弟 5.祖先 6.树的度 7.结点的层次 8.树的深度 9.有序树和无序树 10.森林 三、树的逻辑结构 四、树的存储结构 1.顺序存储 2.链式存储 五、二叉树 1.定义 2.二叉树的五种状态 …

PPT职场课:话术+技巧+框架+案例,告别只会念PPT不会讲(8节课)

课程目录 001-讲PPT如何开场及导入?5个简单实用的方法.mp4 002-讲PPT如何过渡衔接结尾?6类话术争来就用.mp4 003-掌握这3个逻辑表达万能框架,搞定98的PPT.mp4 004-学会这3种PPT结构讲解技巧告别只会念不会讲(上).mp4 005-学会这3种PPT结构讲解技巧告别只会念…