充分了解JavaScript中的对象,顺便弄懂你一直不明白的原型和原型链

  1. 通过Object.creat()创建对象
  • 对象直接量

这种创建方式是我们最常见的,也是最常用的

let obj1 = {} //创建了一个空的对象

let obj2 = { //创建了有一个属性为name,值为张三的对象

name: ‘张三’

}

这种方式创建对象有一种缺点,比如在一个会重复调用的函数里,用了对象直接量的方式创建对象, 会重复创建很对的新对象,并且每次创建的对象的属性值也有可能不同。所以在实际应用中,如果遇到此类情况,尽量避免使用对象直接量的方式创建对象。

  • 通过new创建对象

这种创建对象的方式,一般都是一个new运算符后面跟随一个函数调用,并且该函数有一个名字,叫做构造函数,顾名思义,就是用于构造创建一个对象。

let arr = new Array()

let data = new Date()

这几个都是通过new调用了一个内置对象的构造函数,创建了新的对象实例,我们其实也可以自己定义一个构造函数,然后也通过new的方式来调用我们自定义的构造函数来创建一个对象实例,例如

function MyObj() {

}

let obj = new MyObj() //成功调用构造函数 MyObj,创建了一个对象

  • 通过Object.create()创建对象

这种方式是ES5中规定的新的创建对象的一种方式,只需要简单的传一个原型对象,即可创建对应的新的对象,例如

let obj = Object.create({x:1, y:2}) //{x:1, y:2}是obj的原型

三、对象的原型以及原型链


刚在讲解创建对象的方法时,我们在介绍Object.creat()时,提到了原型的概念,在这里我们就来解释一下,什么是对象的原型。

每一个对象(除了null)都和另一个对象有关联,“ 另一个对象 ” 就叫做原型。 在这里我们可以做个形象的比喻,将原型比作一家餐饮店,将对象比作这家餐饮店的加盟店,这样理解起来就很容易了,我们创建了一个对象,就相当于我们开这家餐饮店的加盟店,并且我们的食材配方 、经营方法都是来自于这家加盟店,所以这里我们可以引入继承的概念,说是加盟店继承于这家餐饮店。

在JavaScript中,绝大部分的对象都有一个共同的原型,他就是 Object.prototype ,也就是说 Object.prototype 是最原始的那家餐饮店,而非加盟店。

每个函数的内部都有一个属性,叫做 prototype,他表示该函数的原型对象,例如

//先写一个构造函数

function MyObj() {

//这里暂时先不写任何代码

}

console.log(typeof MyObj) // function

console.log(typeof MyObj.prototype) // Object

从这个例子中可以看出,MyObj.prototype 返回的是一个对象类型的值,所以这就表示了它是该构造函数的原型对象。

每个对象(除了null)都有一个属性——__proto__ ,该属性表示的是该对象的原型,我们来举两个例子,这两个例子是分别不同方式创建对象后,展示他们各自的原型

  • 用对象直接量创建的对象的原型

let arr = [] //对象直接量创建对象

console.log(arr.proto === Array.prototype) // true

console.log(arr.proto.proto === Object.prototype) //true

console.log(arr.proto.proto.proto) // null

arr.__proto__ 表示的是arr这个对象的原型,而我们都知道对象直接量其实是一种语法糖的写法,在这个例子中 let arr = [] 间接调用了 new Array ,所以我们可以通过 Array.prototype 来表示 Array 这个构造函数的原型对象, 将 arr.__proto__Array.prototype 做比较,发现它俩相等,所以 arr 的原型就是构造函数Array的原型对象。

那么 arr.__proto__.__proto__ 是为了表示 arr 的原型(Array.prototype)的原型,因为我们上面说过,绝大多数的对象都有一个共同的原型 Object.prototype , 所以我们判断一下 Array.prototype 的原型是否就是 Object.prototype,从结果来看,确实是的。

最后我们再去寻找 Object.prototype 的原型(arr.__proto__.__proto__.__proto__)就找不到了,因为 Object.prototype 我们看作是一家餐饮店的源头啊,他并不是谁的加盟店,所以最终返回 null。

在这里我们就可以引入一个概念,叫做原型链。顾名思义,就是一条链子上有很多的原型,如图

在这里插入图片描述

  • 通过Object.create()创建对象

let arr = Object.create({x:1, y:2})

console.log(arr.proto) // {x:1, y:2}

console.log(arr.proto.proto === Object.prototype) // true

console.log(arr.proto.proto.proto) // null

在第三种创建对象方式中,我们说到,用Object.create()创建对象,只需要传入一个原型对象,就可以创建一个继承于该原型对象的新对象。 所以 arr 的原型(arr.__proto__)就是我们传入的那个对象,即 {x:1, y:2}

arr.__proto__.__proto__ 返回的是 {x:1, y:2} 的原型。我们都知道{x:1, y:2} 这样的对象是通过对象直接量创建的,所以他其实是通过 new Object() 来创建的对象, 那么 {x:1, y:2} 的原型就为 Object.prototype 了。

同样的, Object.prototype 没有原型,所以最后返回 null 。

此时的原型链是这样的,如图

在这里插入图片描述

四、对象的属性


定义: 一个对象内部的每个名/值对就是该对象的一个属性,例如 {x:1 ,y:2} 中, x:1 就是该对象的一个属性。

属性有两种类型:

  1. 自有属性: 直接定义在对象中的属性,例如 let obj = {x:1}中,属性x就是该对象的自有属性

  2. 继承属性: 在对象的原型中定义的属性,例如 Object.prototype 中的属性 toString

属性特性一共有四种:

  1. 值: 顾名思义,表示该属性的值

  2. 可写性: 表示是否可以设置该属性的值

  3. 可枚举性: 表示是否可以通过 for / in 循环返回属性的值

  4. 可配置性: 表示是否可以删除或修改该属性

注意:这里列举了 属性的类型属性的特性 ,在下面讲解属性的相关知识时,都会涉及到,所以大家请先尽力记住,这对下面的理解有帮助。

(1)属性的查询与设置

当我们要设置或者获取一个对象的属性时,我们一般都是这么做的

let obj = {

name: ‘Lpyexplore’,

age: 21,

gender: 0

}

let name = obj.name //获取obj对象中的name属性 Lpyexplore

let age = obj[“age”] //获取obj对象中的age属性 21

let fn = obj.toString //获取obj对象中的继承属性toString [Function: toString]

obj[“like”] = “python” //给obj对象设置一个like属性,值为python

obj.weight = 128 //给obj对象设置一个weight属性,值为128

obj.age = 22 //修改obj对象中的age属性,值改为22

console.log(obj.height) //获取obj对象中的height属性

console.log(obj) //查看一下obj对象的变化

/*

undefined

{

name: ‘Lpyexplore’,

age: 22,

gender: 0,

like: ‘python’,

weight: 128

}

*/

从这个例子中我们可以看到,查询一个对象的属性有两种方式,第一种是通过点(.)来访问到某属性,即 对象.属性名 ; 第二种是通过方括号([ ])来访问某属性,即 对象[属性名]。 当访问对象中不存在的属性时,会返回undefined

那么给对象设置一个属性值就更简单了,查询到该属性值后,直接给它赋值就可以设置属性值(若对象内不存在该属性)或修改属性值(对象内已存在该属性值)

(2)属性的删除

删除对象的自有属性,需要用到运算符 delete,直接来看例子

let obj = {

x:1,

y:2,

z:3

}

delete obj.x //删除对象obj中的属性x,返回true

delete obj[“y”] //删除对象obj中的属性y,返回true

delete obj.toString //toString是对象obj的原型中的属性,属于继承属性,无法删除该属性,但仍然返回true

console.log(obj.x) //查询对象obj中的属性x

console.log(obj) //查看对象obj

/*

undefined

{z:3}

*/

在这个例子中可以看到,我们准备删除对象obj中的继承属性toString时,未做任何操作,所以 delete 只能删除对象的自有属性

(3)属性的检测

我们有时需要检测对象的属性,即判断该对象中是否有某个属性 、该属性是否为该对象的自有属性 、该对象是否是可枚举的等等

  • 通过 in 运算符判断属性是否存在

let obj = {

x: 1,

y: undefined

}

“x” in obj //返回true,表示对象obj中有属性x

“y” in obj //返回true,表示对象obj中有属性y,只不过值为undefined

“z” in obj //返回false,表示对象obj中不存在属性z

delete obj.x //删除对象obj中的x属性

“x” in obj //返回false,表示对象obj中不存在属性x

  • 通过对象的 hasOwnProperty( )方法判断属性是否为自有属性

let obj = {

x:1

}

obj.hasOwnProperty(“x”) //返回true,表明属性x存在,且为obj的自有属性

obj.hasOwnProperty(“z”) //返回false,属性zu不存在于对象obj中

obj.hasOwnProperty(“toString”) //返回false,属性toString是obj的继承属性,不是自有属性

  • 通过对象的 propertyIsEnumerable( )方法判断属性是否为自有属性,且该属性具有可枚举性

let obj = Object.create({x:1}) //新建一个对象obj,继承于对象 {x:1}

obj.y = 2 //给obj对象设置一个属性y,其值为2

obj.propertyIsEnumerable(“y”) //返回true,表示属性y为该对象的自有属性,且具有可枚举性

obj.propertyIsEnumerable(“x”) //返回false,因为属性x是继承属性,继承于对象{x:1}

Object.prototype.propertyIsEnumerable(“toString”) //返回false,虽然属性toString是对象Object.prototype的自有属性,但它不具有可枚举性

这里提到了可枚举性, 我们来举个例子

let obj = {

x:1,

y:2,

z:3

}

for(let i in obj) { //遍历对象obj的自有属性

console.log(i)

}

// 输出 1 2 3

可以看到对象obj的自有属性都被 for / in 全部遍历了出来,这就是可枚举性的体现。

其实在ES5中,提供了两个便利属性的函数,我们来了解一下

  • Object.keys( )

这个函数是会返回一个数组,数组中的元素就是对象中可枚举的自有属性名,来看一下例子

let obj = {

x:1,

y:2,

z:3

}

Object.keys(obj) // [‘x’, ‘y’, ‘z’]

  • Object.getOwnPropertyNames( )

这个函数与Object.keys() 类似,区别就在于,该函数返回的是对象中所有自有属性的名称,即不管属性是否具有可枚举性都能被返回。

let obj = {

x:1,

y:2,

z:3

}

Object.getOwnPropertyNames(obj) // [‘x’, ‘y’, ‘z’]

注意: 因为我们还没有讲到如何将一个属性变为可枚举或变为不可枚举,所以这两个函数的区别没办法很好的体现,接下来我们就来讲解一下如何设置属性的三个特性,其中也包括如何设置可枚举性。等了解完如何设置属性的可枚举性后,我们再来尝试一下这两个函数的区别,应该就很好理解了。

(4)特殊的属性

常见的对象属性一般都是名/值对的形式,即 x:1 这样的,我们把这种形式的属性叫做数据属性。在ES5中,提供了一种新的属性形式,叫做存储器属性,该属性可以用两种方法定义,他们分别是 getter 和 setter ,存储器属性在对象中的存在形式不是名/值对的样子,而是类似于我们平时定义函数的样子,function fn() {}。定义存储器属性就是用get(getter)或set(setter) 代替关键字 function,fn就是该属性的名字,有点抽象,来看例子吧

let obj = {

x:1,

y:2,

get r() { //用getter方法定义了属性r,在查询该属性时,调用该函数

return this.x + 2

},

set r(data) { //用setter方法定义了属性r,在给属性r赋值时,调用该函数,并将值作为该函数的参数

this.y * data;

return this.y

}

}

let r = obj.r //查询对象obj中的属性r,返回 3

obj.r = 7 //给对象obj的属性r赋值为7, 返回 14

从上面这个例子中可以得出以下的结论

  1. 用getter方法定义了属性,在查询该属性值时,会调用getter方法定义的函数名为该属性的函数

  2. 用setter方法定义了函数,在给该属性赋值时,会调用setter方法定义的函数名为该属性的函数

  3. 通过前两条结论,可以知道,如果一个存储器属性具有getter方法,则该属性可读;如果具有setter方法,则该属性可写;同时拥有两个方法的话,则该属性是一个可读写的属性。

(5)属性的特性

在第四部分的开头,我们说了属性有四个特性,即值 、可写性 、可枚举性 、可配置性,忘记了的小伙伴翻到前面再看一下。

一般我们创建的数据属性,都是具有这四个特性的(值 、可写性 、可枚举性 、可配置性), 存储器属性是不具有值和可写性两个特性的,但他也具有四个特性,他们分别为:读取(get)、写入(set)、可枚举性 和 可配置性。

在这里我们先引入一个概念,也是ES5定义的一个对象,叫做属性描述符,这个对象就代表了属性的四个特性。属性描述符对象里的属性有 value(值)writable(可写性)enumerable(可枚举性)configurable(可配置性)get(读取)set(写入)

通过 Object.getOwnPropertyDescriptor( ) 可以获得一个对象中某个属性的属性描述符。该方法第一个参数为对象,第二个参数为需要查询的该对象中的属性名。接下来我们来实战一下

  • 查看数据属性的属性描述符

//先创建一个对象obj

let obj = {

x:1

}

Object.getOwnPropertyDescriptor(obj, “x”) //查询对象obj中属性x的属性描述符

// 返回 {value: 1, writable: true, enumerable: true, configurable: true}

  • 查看存储器属性的属性描述符

//先创建一个对象obj

let obj = {

get r() {

return this.x + 1

},

set r(data) {

this.x = data

}

}

Object.getOwnPropertyDescriptor(obj, “r”) //查询对象obj中属性r的属性描述符

// 返回 {get: [Function: get r], set: [Function: set r], enumerable: true, configurable: true}

getOwnPropertyDescriptor()只能获取到一个属性的属性描述符,如果我们想要修改某个属性的特性的话,我们需要用到另一个方法,即 Object.defineProperty() ,他的第一个参数是对象;第二个参数是需要创建或者修改的属性名;第三个参数是属性描述符对象。

直接来看两个实战例子

  • 修改对象中属性的特性

//创建一个对象

let obj = {

x:1

}

//先用propertyIsEnumerable()来测试一下对象obj中的属性x是否还具有可枚举性

obj.propertyIsEnumerable(“x”) //返回true,说明此时属性x是具有可枚举性的

//修改对象obj中属性x的属性特性

Object.defineProperty(obj, “x”, {

value: 2, //属性x的值变为2

writable: true, //属性x具有可写性

enumerable: false, //属性x不具有可枚举性

configurable: true //属性x具有可配置性

})

//利用 propertyIsEnumerable()来测试一下对象obj中的属性x是否还具有可枚举性

obj.propertyIsEnumerable(“x”) // 返回 false,说明对象obj中属性x已经不具有可枚举性了

  • 给对象创建一个属性,并设置该属性的特性

//创建对象obj

let obj = {

x:2

}

//给对象obj创建一个属性,并配置好该属性的特性

Object.defineProperty(obj, “r”, {

get: function() {return this.x + 1}, //给存储器属性r设定一个get函数

set: function(data) {this.x *= data}, //给存储器属性r设定一个set函数

enumerable: true, //存储器属性r具有可枚举性

configurable: true //存储器属性r具有可配置性

})

//查询对象obj中的属性r

obj.r // 返回 3

//给对象obj中的属性r赋值

obj.r = 3

//查询对象obj中的属性x的值

obj.x //返回 6

  • 给对象同时创建多个属性,并为每个属性配置属性特性

这里要用到另一个方法,即 Object.defineProperties() ,这个方法跟 Object.defineProperty() 类似。前者一共有两个参数,第一个参数为对象;第二个参数为一个对象,并且该对象内部是以名/值对的形式存在的,即 需要修改的属性名: 属性描述符对象。接下来我们直接来看实战例子

let obj = {

x:1

}

Object.defineProperties(obj, {

x: {

value: 3,

writable: true,

enumerable: true,

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
,这个方法跟 Object.defineProperty() 类似。前者一共有两个参数,第一个参数为对象;第二个参数为一个对象,并且该对象内部是以名/值对的形式存在的,即 需要修改的属性名: 属性描述符对象。接下来我们直接来看实战例子

let obj = {

x:1

}

Object.defineProperties(obj, {

x: {

value: 3,

writable: true,

enumerable: true,

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-gaHWtseN-1715829877611)]

[外链图片转存中…(img-ueIvdKAp-1715829877611)]

[外链图片转存中…(img-bGNkMC8K-1715829877612)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值