Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript中的对象 #1

Open
Abiel1024 opened this issue Mar 16, 2018 · 0 comments
Open

JavaScript中的对象 #1

Abiel1024 opened this issue Mar 16, 2018 · 0 comments

Comments

@Abiel1024
Copy link
Owner

Abiel1024 commented Mar 16, 2018

在学习JavaScript的道路中,面向对象总是处于一种若即若离的状态。想要说清楚却也总是觉得说不清楚,回头看资料又有恍然大悟的感觉。所以今天就整理了下对象的奥秘。

首先,对象是什么?

在ECMAScript-262中,对象被定义为“无序属性的集合,其属性可以包含基本值,对象或者函数”。
所以我们可以把ECMAScript的对象想象成散列表,无非就是一组名值对,这其中的值可以是数据也可以是函数。
话不多说,我们用例子来了解对象。在控制台里输入:

   var person = new Object()

对没错,你已经创建了一个对象了。是不是还有点小激动,但是光有个对象还不行呀,还得有属性才行,通过这样的方式去设置它的属性。

  person.name = “Mary”
  person.age = 28
  person.job = 'doctor'
  person.sayName = function(){
     console.log(this.name)
  }

这样我们就为People添加了三个属性和一个方法。当然这么写肯定很麻烦,我们可以用对象字面量语法来写

  var person = {
     name: "Mary",
     age: 28,
     job: "doctor",
     sayName: function(){
       console.log(this.name)
     }
  }

完成了对象的赋值,那么接下来我们就可以对他进行读取了,以及执行对象中的方法了。

  console.log(person.name)     // Mary
  console.log(person.age)      // 28
  console..log(person.job)     // doctor
  person.sayName()             // Mary

新增有了,查看有了,那接下来就删和改了。

  person.age = 29
  console.log(person.age)   //29
  delete person.age
  console.log(person.age)   //undefined

通过上面的操作就能完成对对象数据的修改和删除了。增删改查也算是都有了。那么是所有的数据都可以改,也可以删除吗?这就涉及到对象的属性了。

属性

在ECMAScript中,有两种属性:数据属性访问器属性

先说说数据属性,数据属性是指包含数据值的位置,可以有读取和写入值的操作。上面也说到了是不是所有的都可以读取和写入呢?这就数据属性的4个特性来起作用了。这四个属性分别是[[Configurable]]、[[Enumerable]]、[[Writabel]]和[[Value]]。为啥用[[]]给他括起来呢?这是为了表示特性是内部置,作为规范给他们御赐了宝座。毕竟特性是为了实现JavaScript引擎用的,不可以在JavaScript中直接访问他们。所以你在控制台里面是看不到他们的。既然他们位高权重,我们就来分析下他们到底有什么作用。

先看下官方的描述:

  • [[Configurable]]:表示是否可以用delete删除属性从而进行重新定义,能否修改属性的特性,或着能否把属性修改为访问器属性。默认值是true。
  • [[Enumerable]]:表示能否通过for-in循环返回属性。默认值是true。
  • [[Writable]]:表示能否修改属性的值。默认值是true。
  • [[Value]]:包含这个属性的数据指。读取数据的时候,从这个位置读;写入属性值的时候,吧心智保存在这个位置。默认值是undefined。

官方的描述总是让人有点半知半解,通过代码,就能很清楚的了解这特性具体用法。

  var person = {
     name: "Mary"
  }
  console.log(person.name)   //Mary

  var person1 = {}
  Object.defineProperty(person1, "name", {
     writable: false,
     value: "Mary"
  })
  console.log(person1.name)   //Mary
  person1.name = "Bob"
  console.log(person1.name)   //Mary

  var person2 = {}
  Object.defineProperty(person2, "name", {
     configurable: false,
     value: "Mary"
  })
  console.log(person2.name)   //Mary
  delete person2.name
  console.log(person2.name)   //Mary

通过代码,就很清楚了。具体每一值的特性的用途。不过还需要注意的是以上的代码是在非严格模式下进行的,如果在严格模式下,进行就会抛出错误。另外还有一点就是,如果你将configurable属性设置成false了,多次调用Object.defineProperty方法就会收到限制,也不能再次修改configurable的值,会抛出错误。

访问器属性
访问器属性是不包含数据值,它们包含一对getter和setter函数(不过都不是必须的)。在读取访问器属性时,会调用getter函数;在写入访问器属性时,会调用setter函数并传入新值。同时,访问器属性也有四个特性。[[Configurable]]、[[Enumerable]]、[[Get]]和[[Set]]。

[[Configurable]]和[[Enumerable]]特性与数据属性是一样的就不重复。[[Get]],[[Set]]分别是在读取和写入属性时调用的函数。默认值都是undefined。

还是通过代码来了解:

  var person = {
     name: "Mary",
     startWordAge: 26,
     experience: 2,
     _age: 28
  }
  Object.defineProperty(person, "age", {
     get: function(){
        return this._age
     },
     set: function(newValue){
        if(newValue>26){
           this._age = newValue
           this.experience = newValue - 26
        }
     }
  })
  person.age = 29
  console.log(person.experience) // 3

构造函数

在对对象的属性以及属性的特性都有了一定的了解,我们就可以随意的创建和使用对象了。可有时候我们希望我们自己定义的对象有一些私有的属,就可以不用每次都写复制一遍,这就涉及到了构造函数了。比如说工程在制造机器人,每个机器人都会打招呼,但是他们的编号都不一样。如果按照这样的方式肯定是很蠢的!

  var Mary= {
     id: 85756,
     name: 'Mary',
     sayName: function() {
         console.log(this.name)
     }
  };

  var Bob= {
     id: 85757,
     name: 'Bob',
     sayName: function() {
         console.log(this.name)
     }
  }

我们可以这么写:

  var Robot= function(id, name) {
      this.id= id;
      this.name = name;
      this.getName = function() {
         console.log(this.name)
      }
  }
  var Mary = new Robot('85756', 'Mary')
  var Bob= new Robot('85756', 'Bob')

原型

先看看官方对于原型的解释:JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

这么说肯定有点懵逼,试着在控制台输出一个对象。
image

我们就能看到对象中除了我们自己定义的属性和方法之外,还有一个 _proto_ 的属性。对象就是通过 _proto_ 去连接它的原型的。我们可以在 _proto_属性中看到来自以Object对象的方法与属性。这样,原型对象的方法与属性就变成了共有方法与属性。
例如我们查看 obj.valueOfObject.valueOf 是一样的。
image
现在我们可以用新的方式去实现构造函数中实现的功能。

  var Robot= function(id, name) {
      this.id= id;
      this.name = name;
  }
  Robot.prototype.getName = function() {
     console.log(this.name)
  }
  var Mary = new Robot('85756', 'Mary')
  var Bob= new Robot('85756', 'Bob')

这样不同的写法有什么区别吗?回顾构造函数中的方式,每创建一个对象就有一个getName的方法被创建,由于属于不同的实例,就得不停地为这个函数而分配空间,这显然是不合理的。通过原型,我们只需在原型中添加一个getName的方法就能解决这个问题了。

肯定又会有人问为什么能通过prototype为构造函数添加方法呢?
这里很关键!
我们必须理解原型对象。无论在什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下。所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个执行prototype属性所在函数的指针。
可以通过一个例子来了解。借用 这波能反杀的博客 (如侵必删!画图实在是麻烦!!!)

  function Person(name, age) {
      this.name = name;
      this.age = age;
  }
  Person.prototype.getName = function() {
      return this.name;
  }
  var p1 = new Person('tim', 10);
  var p2 = new Person('jak', 22);

image

通过图表我们就能一目了然了。
image

我们再来看下,通过构造函数生成的实例对象。
image

可以看到实例对象的 _proto_是指向构造函数的。这样就可以通过实例去调用构造函数的方法。如果再看构造函数的 _proto_,它是指向对象的。所以function其实是基于对象的一个实例。通过 _proto_ 你会发现,不管是Array、Object、Sting、Number,最终都是指向同一个东西,那就是Object。所以万物皆对象啊!!!
明白了这一点,你知道对象的重要性了吧!

这样就又会涉及一个问题,就是调用先后的问题。
在我们访问实例对象的属性和方法时,会先从实例的属性和方法上寻找,如果寻找不到,就会从原型上去找,如果还是找不到会返回undefined。为啥呢?因为在对象的特性里,value的默认值就是undefined啊!

原型链

说完了原型,那接下来肯定就是原型链了。
原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
先看一个例子

  function Person(name) {
      this.name = name;
  }
  Person.prototype.getName = function() {
      return this.name
  }
  function Children(name) {
      this.name = name;
  }
  Children.prototype = new Father()      //继承了person
  Children.prototype.playGame = function() {
      return "let's play Game"
  }

  var man = new Person('man');
  var bob = new Children('bob');
  bob.getName()     // bob
  bob.playGame()   // let's play Game

这样我们再来看看bob是怎么样的。
image
首先bob是Children的实例,name是bob。Children的原型又指向Person,同时原型方法playGame。Person又有原型方法getName。所以在调用bob.getName()方法时,现在bob中找该方法,没有找到则会从原型Children中找,仍没找的,继续从原型Children的原型Person中找,找到了该方法,然后调用。

这时我们再通过控制台看Children创建的prototype属性是指向什么?
image
毫无疑问 Children的prototype属性是指向Person

通过原型对象等于另一个类型的实例,另一个原型又是另一个类型的实例。这样一层一层的递进,就形成了原型链。

从对象创建、属性、特性,然后再是构造函数、原型和原型链。有点啰嗦,但是总算是基本上梳理了下对象的基本知识。写文章还查了下之前的书,收货还是蛮多的,不知道你们对于对象的理解是不是更加清晰了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant