资讯专栏INFORMATION COLUMN

对JavaScript对象的理解

aboutU / 604人阅读

摘要:面向对象编程语言的对象体系,不是基于类的,而是基于构造函数和原型链。一语言使用构造函数作为对象的模板。构造函数之所以叫构造函数,就是说这个函数的目的,就是操作一个空对象即对象,将其构造为需要的样子。

Javascript
前言:关于javascript我总有些疑问,是不是一门面向对象的语言,总结了很久:Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,其实主要是因为它没有提供象抽象、继承、重载等有关面向对象语言的许多功能, 而是把其它语言所创建的复杂对象统一起来,从而形成一个非常强大的对象系统。 这种独特性称它为prototype-basedOO(基于原型的面向对象)。  (这是针对ES6以前的标准) 

在这篇博客中我引用了阮一峰老师的教程:http://javascript.ruanyifeng.com/oop/basic.html 这个教程讲得十分详细,如果有系统学习的需要,建议跳过我的文章,直接看这里。

js面向对象编程

JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。

一、JavaScript 语言使用构造函数(constructor)作为对象的模板。

var User = function () {

this.age = 23;
this.name = "wanghaoxin";

};

这是一个构造函数。

构造函数的写法就是一个普通的函数,但是有自己的特征和用法。

函数体内部使用了this关键字,代表了所要生成的对象实例。

生成对象的时候,必需用new命令,调用Vehicle函数。

var user = new User();//这里的等同于 var user = new User;
user.age = 123;
console.log(user.age);
console.log(user.name);

执行这段代码结果就是 123 、 wanghaoxin,这里的user.age经过了赋值,所以是赋值后的值。

由于js并不是java一样的强类型语言,所以不能再编译前检查你的语法错误,这里有个问题。

> 一个很自然的问题是,如果忘了使用new命令,直接调用构造函数会发生什么事?

这种情况下,构造函数就变成了普通函数,并不会生成实例对象。而且由于后面会说到的原因,this这时代表全局对象,将造成一些意想不到的结果。

二、new命令的原理

使用new命令时,它后面的函数调用就不是正常的调用,而是依次执行下面的步骤。

    1. 创建一个空对象,作为将要返回的对象实例. 
    2. 将这个空对象的原型,指向构造函数的prototype属性.
    3. 将这个空对象赋值给函数内部的this关键字.
    4. 开始执行构造函数内部的代码.

也就是说,构造函数内部,this指的是一个新生成的空对象,所有针对this的操作,都会发生在这个空对象上。构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即this对象),将其“构造”为需要的样子。

如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。

三、使用 Object.create() 创建实例对象

构造函数作为模板,可以生成实例对象。但是,有时只能拿到实例对象,而该对象根本就不是由构造函数生成的,这时可以使用Object.create()方法,直接以某个实例对象作为模板,生成一个新的实例对象。

    var person1 = {
      name: "张三",
      age: 38,
      greeting: function() {
        console.log("Hi! I"m " + this.name + ".");
      }
    };
    
    var person2 = Object.create(person1);
    
    person2.name // 张三
    person2.greeting() // Hi! I"m 张三.

四、javascript使用prototype

JavaScript 的每个对象都继承另一个对象,后者称为“原型”(prototype)对象。null也可以充当原型,区别在于它没有自己的原型对象。JavaScript 继承机制的设计就是,原型的所有属性和方法,都能被子对象共享。

function Animal(name) {
  this.name = name;
}

Animal.prototype.color = "white";

var cat1 = new Animal("大毛");
var cat2 = new Animal("二毛");

cat1.color // "white"
cat2.color // "white"

这里的color属性被两个猫共享了。

cat1.color = "black";

cat1.color // "black"
cat2.color // "yellow"
Animal.prototype.color // "yellow";

在这里cat1.color 被赋值为 black ,所以cat1将不再使用原来的原型属性.

prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。

构造函数就是普通的函数, 所以实际上所有函数都有prototype属性。

五、_proto_原型链

对象的属性和方法,有可能定义在自身,也有可能定义在它的原型对象。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype chain)。比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。

如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。那么,Object.prototype对象有没有它的原型呢?回答是有的,就是没有任何属性和方法的null对象,而null对象没有自己的原型。

“原型链”的作用是,读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。

如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。

Object.prototype.__proto__

根据语言标准,__proto__属性只有浏览器才需要部署,其他环境可以没有这个属性,而且前后的两根下划线,表示它本质是一个内部属性,不应该对使用者暴露。因此,应该尽量少用这个属性,而是用Object.getPrototypeof()(读取)和Object.setPrototypeOf()(设置),进行原型对象的读写操作。

this及this的绑定

this的动态切换,固然为JavaScript创造了巨大的灵活性,但也使得编程变得困难和模糊。有时,需要把this固定下来,避免出现意想不到的情况。JavaScript提供了call、apply、bind这三个方法,来切换/固定this的指向。

this的使用

这个关键字很好的体现了,如果不是面向对象,那为什么使用this呢,其实这个this还真有点不一样,在java中this就是指的本类,那么js中呢?

this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的。

所以这里需要注意的问题就是,到底是哪个对象最后调用了this,只有因为this本身指代的就是对象。下面的例子中this的指向变了,变成了谁(谁调用了), 一目了然。

function f() {
  return "姓名:"+ this.name;
}

var A = {
  name: "张三",
  describe: f
};

var B = {
  name: "李四",
  describe: f
};

A.describe() // "姓名:张三"
B.describe() // "姓名:李四"

如果没有对象调用他,默认是用window对象调用的,这也讲得通,因为window是js的顶层对象,所有其他对象都是window的下属对象,JavaScript规定,浏览器环境的所有全局变量,都是window对象的属性。如果一个函数在全局环境中运行,那么this就是指顶层对象。

具体的例子看下面链接,比我写的好,
这里有详细的讲解http://www.cnblogs.com/pssp/p/5216085.html

call的使用

函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。call方法的参数,应该是一个对象。如果参数为空、null和undefined,则默认传入全局对象。

var obj = {};

var f = function () {
  return this;
};

f() === this // true
f.call(obj) === obj // true

上面代码中,在全局环境运行函数f时,this指向全局环境;call方法可以改变this的指向,指定this指向对象obj,然后在对象obj的作用域中运行函数f。

var n = 123;
var obj = { n: 456 };

function a() {
  console.log(this.n);
}

a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456

上面代码中,a函数中的this关键字,如果指向全局对象,返回结果为123。如果使用call方法将this关键字指向obj对象,返回结果为456。可以看到,如果call方法没有参数,或者参数为null或undefined,则等同于指向全局对象。

function add(a, b) {
  return a + b;
}

add.call(this, 1, 2) // 3

上面代码中,call方法指定函数add内部的this绑定当前环境(对象),并且参数为1和2,因此函数add运行后得到3。

var obj = {};
obj.hasOwnProperty("toString") // false

// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
  return true;
};
obj.hasOwnProperty("toString") // true

Object.prototype.hasOwnProperty.call(obj, "toString") // false

上面代码中,hasOwnProperty是obj对象继承的方法,如果这个方法一旦被覆盖,就不会得到正确结果。call方法可以解决这个问题,它将hasOwnProperty方法的原始定义放到obj对象上执行,这样无论obj上有没有同名方法,都不会影响结果。

apply的使用

apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。

function f(x,y){
  console.log(x+y);
}

f.call(null,1,1) // 2
f.apply(null,[1,1]) // 2

上面的 f 函数本来接受两个参数,使用apply方法以后,就变成可以接受一个数组作为参数。

绑定回调函数的对象

var o = new Object();
o.f = function () {
console.log(this === o);
}
var f = function (){
o.f.apply(o);
// 或者 o.f.call(o);
};
$("#button").on("click", f);

点击按钮以后,控制台将会显示true。由于apply方法(或者call方法)不仅绑定函数执行时所在的对象,还会立即执行函数,因此不得不把绑定语句写在一个函数体内。更简洁的写法是采用下面介绍的bind方法。

bind的使用

bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。

var counter = {
  count: 0,
  inc: function () {
    this.count++;
  }
};

var func = counter.inc;
func();
counter.count // 0
count // NaN

上面代码中,函数func是在全局环境中运行的,这时inc内部的this指向顶层对象window,所以counter.count是不会变的,反而创建了一个全局变量count。因为window.count原来等于undefined,进行递增运算后undefined++就等于NaN。

为了解决这个问题,可以使用bind方法,将inc内部的this绑定到counter对象。

var func = counter.inc.bind(counter);
func();
counter.count // 1

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107338.html

相关文章

  • 面向 JavaScript

    摘要:是完全的面向对象语言,它们通过类的形式组织函数和变量,使之不能脱离对象存在。而在基于原型的面向对象方式中,对象则是依靠构造器利用原型构造出来的。 JavaScript 函数式脚本语言特性以及其看似随意的编写风格,导致长期以来人们对这一门语言的误解,即认为 JavaScript 不是一门面向对象的语言,或者只是部分具备一些面向对象的特征。本文将回归面向对象本意,从对语言感悟的角度阐述为什...

    novo 评论0 收藏0
  • 加深 JavaScript This 理解

    摘要:使用来调用函数,会自动执行下面操作创建一个全新的对象。所以如果是一个函数的话,会是这样子的创建一个新对象连接新对象与函数的原型执行函数,改变指向新的对象所以,在使用来调用函数时候,我们会构造一个新对象并把它绑定到函数调用中的上。 欢迎来我的博客阅读:《加深对 JavaScript This 的理解》 我相信你已经看过很多关于 JavaScript 的 this 的谈论了,既然你点进来...

    PiscesYE 评论0 收藏0
  • 理解JavaScript核心知识点:原型

    摘要:首先,需要来理清一些基础的计算机编程概念编程哲学与设计模式计算机编程理念源自于对现实抽象的哲学思考,面向对象编程是其一种思维方式,与它并驾齐驱的是另外两种思路过程式和函数式编程。 JavaScript 中的原型机制一直以来都被众多开发者(包括本人)低估甚至忽视了,这是因为绝大多数人没有想要深刻理解这个机制的内涵,以及越来越多的开发者缺乏计算机编程相关的基础知识。对于这样的开发者来说 J...

    iKcamp 评论0 收藏0
  • Javascript】深入理解this作用域问题以及new/let/var/constthis作

    摘要:理解作用域高级程序设计中有说到对象是在运行时基于函数的执行环境绑定的在全局函数中,等于,而当函数被作为某个对象调用时,等于那个对象。指向与匿名函数没有关系如果函数独立调用,那么该函数内部的,则指向。 理解this作用域 《javascript高级程序设计》中有说到: this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象调用时,t...

    snowLu 评论0 收藏0
  • 深入理解JavaScript系列11:根本没有“JSON象”这回事

    摘要:更多资料如果你想了解更多关于的资料,下面的连接对你绝对有用关于本文本文转自大叔的深入理解系列。深入理解系列文章,包括了原创,翻译,转载,整理等各类型文章,原文是大叔的一个非常不错的专题,现将其重新整理发布。 前言 写这篇文章的目的是经常看到开发人员说:把字符串转化为JSON对象,把JSON对象转化成字符串等类似的话题,所以把之前收藏的一篇老外的文章整理翻译了一下,供大家讨论,如有错误,...

    darryrzhong 评论0 收藏0
  • JavaScript闭包

    摘要:闭包引起的内存泄漏总结从理论的角度将由于作用域链的特性中所有函数都是闭包但是从应用的角度来说只有当函数以返回值返回或者当函数以参数形式使用或者当函数中自由变量在函数外被引用时才能成为明确意义上的闭包。 文章同步到github js的闭包概念几乎是任何面试官都会问的问题,最近把闭包这块的概念梳理了一下,记录成以下文章。 什么是闭包 我先列出一些官方及经典书籍等书中给出的概念,这些概念虽然...

    HmyBmny 评论0 收藏0

发表评论

0条评论

aboutU

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<