深入JS面向对象(原型-继承)(一)

简介: 深入JS面向对象(原型-继承)

面向对象是现实的抽象方式

现实世界的东西大多数都是可以在编程中抽象出来的


比如你可以抽象出一个女朋友new GridFriend(),或者抽象出coderwhy或者小满什么的来,可惜只能单向的抽象哈哈,不能映射到现实


编程是对现实世界的抽象,而面向对象是对现实世界抽象的一种方式

  • 对象是JavaScript中一个非常重要的概念,这是因为对象可以将多个相关联的数据封装到一起,更好的描述一个事物:
  • 比如我们可以描述一辆车:Car,具有颜色(color)、速度(speed)、品牌(brand)、价格(price),行驶(travel)等 等
  • 比如我们可以描述一个人:Person,具有姓名(name)、年龄(age)、身高(height),吃东西(eat)、跑步(run) 等等;
  • 对象来描述事物,更有利于我们将现实的事物,抽离成代码中某个数据结构
  • 所以有一些编程语言就是纯面向对象的编程语言,比Java;
  • 你在实现任何现实抽象时都需要先创建一个类,根据类再去创建对象

JavaScript的面向对象

  • JavaScript其实支持多种编程范式,包括函数式编程和面向对象编程:
  • JavaScript中的对象被设计成一组属性的无序集合,像是一个哈希表,有key和value组成;
  • key是一个标识符名称,value可以是任意类型,也可以是其他对象或者函数类型
  • 如果值是一个函数,那么我们可以称之为是对象的方法
  • 如何创建一个对象?
  • 早期使用创建对象的方式最多的是使用Object类,并且使用new关键字来创建一个对象:
  • 这是因为早期很多JavaScript开发者是从Java过来的,它们也更习惯于Java中通过new的方式创建一个对象;
  • 后来很多开发者为了方便起见,都是直接通过字面量的形式来创建对象
  • 这种形式看起来更加的简洁,并且对象和属性之间的内聚性也更强,所以这种方式后来就流行了起来;

创建对象的方式

//创建一个对象,对某个人进行抽象?(描述)
//方式1:通过new Object()创建
var obj1 = new Object()//以前的人喜欢这样创建对象
obj1.name = "洛洛"
obj1.age = 20
obj1.sex = "女"//然后这样抽象属性
//上面的方式是对obj1当作一个构造函数,然后通过new关键字来执行函数,这个时候也会创建出来对象
//方式2:字面量形式
var obj = {
    name:"小余",
    age:20,
    sex:"男",
    eating:function(){
        console.log(this.name+"在吃辣条~")
    }
}//这种创建对象方式叫做对象的字面量,现在更多是这种

对属性操作的控制

  • 在前面我们的属性都是直接定义在对象内部,或者直接添加到对象内部的:
  • 但是这样来做的时候我们就不能对这个属性进行一些限制:比如这个属性是否是可以通过delete删除的?这个 属性是否在for-in遍历的时候被遍历出来呢?
var obj = {
    name:"小余",
    age:20,
    sex:"男"
}
//对属性的控制
//获取属性
console.log(obj.name)//小余
//给属性赋值
obj.name = "xiaoyu"
console.log(obj.name)//xiaoyu
//删除属性
delete obj.name
console.log(obj)//{ age: 20, sex: '男' }


  • 如果我们想要对一个属性进行比较精准的操作控制,那么我们就可以使用属性描述符。
  • 通过属性描述符可以精准的添加或修改对象的属性
  • 属性描述符需要使用 Object.defineProperty 来对属性进行添加或者修改

Object.defineProperty

  • Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
  • 会修改我们的原对象的,所以这个并不是一个纯函数
Object.defineProperty(obj,prop,descriptor)
  • 可接收三个参数:
  • obj:要定义属性的对象
  • prop:要定义或修改的属性的名称或者Symbol
  • descriptor:要 定义或修改的属性描述符
  • 返回值:
  • 被传递给函数的对象
var obj = {
    name:"xiaoyu",
    age:20
}
Object.defineProperty(obj,"height",{
    //很多的配置
    value:1.75
})
console.log(obj)
//node环境下打印
//{ name: 'xiaoyu', age: 20 }

控制台打印:

image.png

出现如上问题的原因:


  • 因为height的属性是不可枚举,不可遍历的。所以我们在node环境下整体打印就看不到新增的height,但是我们可以局部打印还是可以出来的,例如下方的案例,说明这个height已经真真实实的添加到我们的obj里面了,只是我们看不到而已


var obj = {
    name:"xiaoyu",
    age:20
}
Object.defineProperty(obj,"height",{
    //很多的配置,我们在这里写入的就是属性描述符
    value:1.75
})
console.log(obj.height);//1.75

属性描述符分类

  • 属性描述符的类型有两种:
  • 数据属性(Data Properties)描述符(Descriptor);
  • 存取属性(Accessor访问器 Properties)描述符(Descriptor);

configurable(可配置的)

enumerable(可枚举的)

value(值)

writable(可写的)

get(获取)

set(设置)

数据描述符

可以

可以

可以

可以

不可以

不可以

存取描述符

可以

可以

不可以

不可以

可以

可以

数据属性描述符

  • 数据属性描述符有如下四个特征
  • [[Configurable]]:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符
  • 当我们直接在一个对象上定义某个属性时,这个属性的[[Configurable]]为true
  • 当我们通过属性描述符定义一个属性时,这个属性的[[Configurable]]默认为false
  • [[Enumerable]]:表示属性是否可以通过for-in或者Object.key()返回该属性;
  • 当我们直接在一个对象上定义某个属性时,这个属性的[[Enumerable]]为true
  • 当我们通过属性描述符定义一个属性时,这个属性的[[Enumerable]]默认为false
  • [[Writable]]:表示是否可以修改属性的值;
  • 当我们直接在一个对象上定义某个属性时,这个属性的[[Writable]]为true
  • 当我们通过属性描述符定义一个属性的时候,这个属性的[[Writable]]为false
  • [[value]]:属性的value值,读取属性时会返回该值,修改属性时,会对其进行修改;
  • 默认情况下这个值是undefined
//name和age虽然没有使用属性描述符来定义,但是它们也是具备对应的特性的,以下是对应的默认值
//value:赋值的value
//configurable:true
//enumerable:true
//writable:true
var obj = {
    name:"xiaoyu",
    age:18
}
//数据属性描述符
Object.defineProperty(obj,"address",{
    //很多配置
    value:"福建省",//默认值undefined
    //该属性不可删除,不可修改。不可以重新定义属性描述符
    configurable:false//默认值false
    //该特性是配置对应的属性(address)是否是可以枚举的
    enumerable:true,//默认值false
    //该特性是否可以赋值
    writable:false//默认值false
})
delete obj.name
console.log(obj)//{ age: 18 },name被成功删除
delete obj.address
console.log(obj.address);//福建省  没删除掉,因为我们设置了不可配置configurable:false
//测试enumerable的作用
console.log(obj)
for(var key in obj){
    console.log(key,'for遍历');//如果enumerable为false,则只会出来name和age,address只有设置为true的时候才会出来
}
console.log(Object.keys(obj),'keys的作用');
//enumerable前后对比
//[ 'name', 'age' ] keys的作用(enumerable:false)
//[ 'name', 'age', 'address' ] keys的作用(enumerable:true)
//测试writable的作用
obj.address = "上海市"
console.log(obj.address);//福建省,新的内容不可写入。如果我们不设置value为福建省,则在不可写入的情况下显示undefined

存取属性描述符

  • 数据描述符有如下四个特征:
  • [[Configurable]]:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性 描述符
  • 和数据属性描述符是一致的
  • 当我们直接在一个对象上定义某个属性时,这个属性的[[Configurable]]为true;
  • 当我们通过属性描述符定义一个属性时,这个属性的[[Configurable]]默认为false;
  • [[Enumerable]]:表示属性是否可以通过for-in或者Object.keys()返回该属性;
  • 和数据描述符是一致的
  • 当我们直接在一个对象上定义某个属性时,这个属性的[[Enumerable]]为true
  • 当我们通过属性描述符定义一个属性时,这个属性的[[Enumerable]]默认为false;
  • [[get]]:获取属性时会执行的函数,默认为undefined
  • [[set]]:设置属性时会执行的函数,默认为undefined
var obj = {
    name:"xiaoyu",
    age:18,
    _address:"泉州市"//_开头表示私有的,不希望被人看到。我们就通过get来使用address代替掉_address。别人就通过address调用我们,而不是使用_address调用
}
//当我们使用get、set,不使用value和writable的时候,叫做存取属性描述符
//使用场景:1.隐藏某一个私有属性,不希望直接被外界使用和赋值
//2.如果我们希望截获某一个属性,它访问和设置值的过程时,我们也会使用存储属性描述符
Object.defineProperty(obj,"address",{
    //很多配置
    enumerable:true,
    configurable:true,
    //value跟writable与get、set不能共存
    // value:"福建省",
    // writable:true,
    get:function(){
        foo()//我们希望获取值的时候提醒我们一下的时候就会这么干
    return this._address//将_address这个属性隐藏起来,给他套上了address这个马甲
    },
    set:function(value){
        bar()//这样就截获了它获取值的过程,这是Vue2响应式的原理
    //当我们如下对obj.address进行赋值的时候,值就通过形参传递了进来,我们在这里进行赋值的操作
        this._address = value
    }
})
console.log(obj)//{ name: 'xiaoyu', age: 18, address: [Getter/Setter] }
console.log(obj.address);//泉州市,get拿到了值
obj.address = "小满的后宫"//我们使用的是address,而不是_address了哦,注意这里的变化
console.log(obj.address);//小满的后宫
function foo(){
    console.log("获取了一次address的值")
}
function bar(){
    console.log("设置了一次address的值")
}

继承的实现方案、ES6面向对象

对象上同时定义多个属性

  • Object.defineProperties() 方法直接在一个对象上定义 多个 新的属性或修改现有属性,并且返回该对象。
var obj = {
    //私有属性(js中没有严格意义上的私有属性,你依旧可以通过obj._age访问到,但是外人是不知道这个隐藏起来的属性,只知道他的替代obj.age)
    _age:20
}
Object.defineProperties(obj,{
    name:{
        //是否可配置
        configurable:true,
        //是否枚举
        enumerable:true,
        //添加新值
        value:"小余",
        //是否可写入
        writable:true
    },
    age:{
        configurable:false,
        enumerable:false,
        get:function(){
            return this._age
        },
        set:function(value){
            this._age = value
        }
    }
})
console.log(obj.age)//20
console.log(obj,"这是为了看age的不可枚举是否生效");//{ _age: 20, name: '小余' } 这是为了看age的不可枚举是否生效
obj.age = 18
console.log(obj.age);//18
  • 在开发中,如果我们想对某一个属性定义对应的get和set,我们也可以这么做:

这种写法有一点差异,但是性能是差不多的(但如果我们想要更加精准的控制的话,还是采用在defineProperties写的方式好,因为这样子才能配置configurable这类的东西)


差异在于我们这里控制台打印出来的是能够看到age的,而在Object.defineProperties中是看不到的,在下方代码块中我将他们的对比效果贴出来了,放在最底下


var obj = {
    _age:20,
    set age(value){
        this._age = value
    },
    get age(){
        return this._age
    }
}
//以上的age写法就替代了我们在Object.defineProperties的写法
// age:{
//     configurable:false,
//     enumerable:false,
//     get:function(){
//         return this._age
//     },
//     set:function(value){
//         this._age = value
//     }
// }
//差异对比:控制台打印obj
//直接在对象中写法打印效果:
{ _age: 20, age: [Getter/Setter], name: '小余' }//表示的age属性有get和set
//在Object.defineProperties中的get、set打印效果:
{ _age: 20, name: '小余' }


深入JS面向对象(原型-继承)(二)https://developer.aliyun.com/article/1470348

目录
相关文章
|
6天前
|
JavaScript 前端开发
前端 JS 经典:原型和原型链
前端 JS 经典:原型和原型链
11 0
|
9天前
|
前端开发 JavaScript
前端 JS 经典:Class 面向对象
前端 JS 经典:Class 面向对象
11 1
|
9天前
|
前端开发 JavaScript
前端 js 经典:原型对象和原型链
前端 js 经典:原型对象和原型链
20 1
|
10天前
|
JavaScript 前端开发
JavaScript 原型链继承:掌握面向对象的基础
JavaScript 原型链继承:掌握面向对象的基础
|
11天前
|
JavaScript 前端开发 API
在Node.js上使用dojo库进行面向对象web应用开发
请注意,虽然这个例子在Node.js环境中使用了Dojo,但Dojo的许多功能(例如DOM操作和AJAX请求)在Node.js环境中可能无法正常工作。因此,如果你打算在Node.js环境中使用Dojo,你可能需要查找一些适用于服务器端JavaScript的替代方案。
19 0
|
11天前
|
JavaScript 前端开发
在JavaScript中,函数原型(Function Prototype)是一个特殊的对象
【5月更文挑战第11天】JavaScript中的函数原型是一个特殊对象,它为所有函数实例提供共享的方法和属性。每个函数在创建时都有一个`prototype`属性,指向原型对象。利用原型,我们可以向所有实例添加方法和属性,实现继承。例如,我们定义一个`Person`函数,向其原型添加`greet`方法,然后创建实例`john`和`jane`,它们都能调用这个方法。尽管可以直接在原型上添加方法,但推荐在构造函数内部定义以封装数据和逻辑。
21 2
|
11天前
|
JavaScript 前端开发
JavaScript 中最常用的继承方式
【5月更文挑战第9天】JavaScript中的继承有多种实现方式:1) 原型链继承利用原型查找,但属性共享可能引发问题;2) 借用构造函数避免共享,但方法复用不佳;3) 组合继承结合两者优点,是最常用的方式;4) ES6的class继承,是语法糖,仍基于原型链,提供更直观的面向对象编程体验。
25 1
|
11天前
|
设计模式 JavaScript 前端开发
在JavaScript中,继承是一个重要的概念
【5月更文挑战第9天】JavaScript继承有优点和缺点。优点包括代码复用、扩展性和层次结构清晰。缺点涉及深继承导致的复杂性、紧耦合、单一继承限制、隐藏父类方法以及可能的性能问题。在使用时需谨慎,并考虑其他设计模式。
17 2
|
11天前
|
JavaScript 前端开发 开发者
JavaScript 继承的方式和优缺点
JavaScript 继承的方式和优缺点
12 0
|
11天前
|
JavaScript 前端开发
JavaScript 继承的方式和优缺点
JavaScript 继承的方式和优缺点