在Javascript不存在类(Class)的概念,javascript中不是基于类的,而是通过构造函数(constructor)和原型链(prototype chains)实现的。但是在ES6中引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让原型对象的写法更加清晰、更像面向对象编程的语法而已。

1、构造函数的简单介绍

构造函数就是提供了一个生成对象的模板并描述对象的基本结构的函数。一个构造函数,可以生成多个对象,每个对象都有相同的结构。总的来说,构造函数就是对象的模板,对象就是构造函数的实例。

构造函数的特点有:

  a:构造函数的函数名首字母必须大写。

  b:内部使用this对象,来指向将要生成的对象实例。

  c:使用new操作符来调用构造函数,并返回对象实例。

function Person(){
  this.name = 'keith';
}
var boy = new Person();
console.log(boy.name); //'keith'

2、构造函数的缺点

所有的对象实例都可以继承构造函数中的属性和方法。但是,同一个对象实例之间,无法共享属性。

function Person(name, height) {
this.name = name;
this.height = height;
this.hobby = function () {
return 'watching movies';
}
}
var boy = new Person('keith', 180);
var girl = new Person('rascal', 153);
console.log(boy.name); //'keith'
console.log(girl.name); //'rascal'
console.log(boy.hobby === girl.hobby); //false

上面代码中,一个构造函数Person生成了两个对象实例boy和girl,并且有两个属性和一个方法。但是,它们的hobby方法是不一样的。也就是说,每次使用new来调用构造函数生成一个对象实例的时候,都会创建一个hobby方法。这既没有必要,又浪费资源,因为所有hobby方法都是同样的行为,完全可以被两个对象实例共享。

所以,构造函数的缺点就是:同一个构造函数的生成的对象实例之间无法共享属性或方法。

3、prototype属性

为了解决构造函数的对象实例之间无法共享属性的缺点,JS 提供了prototype属性。

原型属性(prototype)是函数特有的属性,这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。通过prototype定义的属性及方法能被所有对象实例所共享,这就是prototype的意义。

通过构造函数生成对象实例时,会将对象实例的原型指向构造函数的 prototype 属性。每一个构造函数都有一个prototype属性,函数的这个属性值就是对象实例的原型对象。

function Person(name, height) {
this.name = name;
this.height = height;
}
Person.prototype.hobby = function () { // 生成的对象实例会共享这样声明的构造函数方法
return 'watching movies';
}
var boy = new Person('keith', 180);
var girl = new Person('rascal', 153);
console.log(boy.name); //'keith'
console.log(girl.name); //'rascal'
console.log(boy.hobby === girl.hobby); //true 表明生成的对象实例共享着同一个方法

上面代码中,如果将hobby方法放在原型对象上,那么两个实例对象都共享着同一个方法。对于构造函数来说,prototype是作为构造函数的属性;对于对象实例来说,prototype是对象实例的原型对象。所以prototype即是属性,又是对象。

只要修改原型对象上的属性和方法,变动就会立刻体现在所有对象实例上。

Person.prototype.hobby = function(){
  return 'swimming';
}
console.log(boy.hobby === girl.hobby); //true
console.log(boy.hobby()); //'swimming'
console.log(girl.hobby()); //'swimming'

上面代码中,当修改了原型对象的hobby方法之后,两个对象实例都发生了变化。这是因为对象实例其实是没有hobby方法,都是读取原型对象的hobby方法。也就是说,当某个对象实例没有该属性和方法时,就会到原型对象上去查找。如果实例对象自身有某个属性或方法,就不会去原型对象上查找。

boy.hobby = function(){
  return 'play basketball';
}
console.log(boy.hobby()); //'play basketball'
console.log(girl.hobby()); //'swimming'

上面代码中,boy对象实例的hobby方法修改时,就不会在继承原型对象上的hobby方法了。不过girl仍然会继承原型对象的方法。

总结:

  a:原型对象的作用,就是定义所有对象实例所共享的属性和方法。

  b:prototype 对于构造函数来说,它是一个属性;对于对象实例来说,它是一个原型对象。

4、原型链(prototype chains)

对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象上。由于原型对象本身也是对象,它也有自己的原型,所以形成了一条原型链(prototype chain)。比如,a的原型对象是b,b的原型对象是c,以此类推。所有一切的对象的原型顶端,都是Object.prototype,即 Object 构造函数的 prototype 属性指向的那个对象。

当然,Object.prototype对象也有自己的原型对象,那就是没有任何属性和方法的 null 对象,而 null 对象没有自己的原型。

console.log(Person.prototype.isPrototypeOf(boy))      //true  假设person是boy的原型
console.log(Object.getPrototypeOf(Object.prototype)); //null 此处表明 Object.prototype 的原型对象是 null

原型链(prototype chain)的特点有:

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

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

  c:一级级向上在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。

下面利用代码来讲解:

var arr = [1,2,3];
console.log(arr.length); //
console.log(arr.valueOf()) //[1,2,3]
console.log(arr.join('|')) //1|2|3

上面代码中,定了一个数组arr,数组里面有三个元素。我们并没有给数组添加任何属性和方法,可是在调用 length,join(),valueOf() 时,却不会报错。

length 属性是继承自 Array.prototype 的,属于原型对象上的一个属性。join 方法也是继承自 Array.prototype 的,属于原型对象上的一个方法。这两个方法是所有数组所共享的。当实例对象上没有这个 length 属性时,就会去原型对象查找。valueOf 方法是继承自Object.prototype 的。首先,arr数组是没有 valueOf 方法的,所以就到原型对象 Array.prototype 查找。然后,发现 Array.prototype 对象上也没有valueOf方法。最后,再到它的原型对象 Object.prototype 查找,Object.prototype 上面有这个方法。

来看看 Array.prototype 对象和 Object.prototype 对象分别有什么属性和方法。

console.log(Object.getOwnPropertyNames(Array.prototype))      //["length", "toSource", "toString", "toLocaleString", "join", "reverse", "sort", "push", "pop", "shift", "unshift", "splice", "concat", "slice", "lastIndexOf", "indexOf", "forEach", "map", "filter", "reduce", "reduceRight", "some", "every", "find", "findIndex", "copyWithin", "fill", "entries", "keys", "values", "includes", "constructor", "$set", "$remove"]
console.log(Object.getOwnPropertyNames(Object.prototype)) // ["toSource", "toString", "toLocaleString", "valueOf", "watch", "unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__", "constructor"]

5、constructor属性

对象实例的constructor属性返回对象实例的构造函数。

function A(){};
var a = new A();
console.log(a.constructor); //A

constructor 属性实际上是原型对象的属性,这个属性包含了一个指针,指回原构造函数,它被所有实例对象继承。

function A(){};
var a = new A();
console.log(A.prototype.constructor === A) //true 通过原型对象访问constructor属性返回的是原型对象所处的构造函数
console.log(a.constructor === A.prototype.constructor); //true
console.log(A.hasOwnProperty('prototype'));             //true
console.log(A.prototype.hasOwnProperty('constructor')); //true

5.1、constructor属性的作用

  a:判断对象实例的构造函数

function A(){};
var a = new A();
console.log(a.constructor === A)    //true
console.log(a.constructor === Array) //false

  b:从实例新建另一个实例

function A() {};
var a = new A();
var b = new a.constructor(); //从a.constructor间接调用构造函数。
console.log(b instanceof A); //true

6、instanceof运算符

instanceof 用于判断对象是否为某个构造函数的实例。

function A() {};
var a = new A();
console.log(a instanceof A); //true
console.log(a instanceof Object); //true 对整个原型链上的对象都有效

注意,instanceof对象只能用于复杂数据类型(数组,对象等),不能用于简单数据类型(布尔值,数字,字符串等)。而且 null 和 undefined 都不是对象,所以instanceof 总是返回false。

console.log([1] instanceof Array);     //true
console.log({} instanceof Object); //true
console.log(true instanceof Boolean); //false
console.log('aaa' instanceof String); //false console.log(null instanceof Object); //false
console.log(undefined instanceof Object); //false

利用instanceof运算符,还可以巧妙地解决,调用构造函数时,忘了加new命令的问题。

function Keith(name,height) {
  if (!this instanceof Keith) {
    return new Keith(name,height);
}
this.name = name;
this.height = height;
}

7、__proto__属性(注意两边都是两个_符号)

属性 __proto__ 返回对象实例的原型对象,即构造函数的 prototype 属性值,通过该属性可以访问原型对象的所有方法。

var Person = function(name){
this.name = name;
}
var a = new Person('jack');
console.log(a.__proto__ === Person.prototype); //true 同样的,Person也有原型对象,通过Person的 __proto__ 属性也可以访问到它的原型对象,以此类推,可以实现原型链的向上追溯。

可以通过 __proto__ 属性继承其他对象的属性,但非常不建议这么做,对性能影响非常大。详情查看:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/proto

var Person = function (name) {
this.name = name;
}
var a = new Person('jack');
console.log(a.name); //jack
var b = {
age: 10
}
a.__proto__ = b;
console.log(a.name,a.age); //jack 10

相对于通过 __proto__ 属性继承其他对象的属性而言,Object.create() 方法是一个更加值得推荐的方法。该方法接收一个对象作为参数,返回一个以该对象为原型对象的新对象,即继承了作为参数的对象的属性及方法。

let b = {
age: 10
}
let a = Object.create(b);
console.log(a.age); //10
 

8、Function.prototype和Object.prototype

Function 和 Object 其实就是一个构造函数。

function Person(a,b){
this.a = a;
this.b = b;
}
let man = new Person('a','b');
console.log(Person.constructor); //输出 ƒ Function() { [native code] }
console.log(Person.constructor.constructor); //ƒ Function() { [native code] }
console.log(Function == Person.constructor); //true 此处表明 Function 就是一个构造函数,并且普通函数的构造函数就是 Function
console.log(Function.prototype == Person.__proto__); //true
let obj = {}
console.log(obj.constructor); //输出 ƒ Object() { [native code] }
console.log(Object == obj.constructor); //true 此处表明 Object就是一个构造函数,并且普通对象的构造函数就是 Object
console.log(Object.prototype == obj.__proto__); //true

面试题:是否所有的对象都继承 Object

不是,JS 中不是所有对象都继承Object,也有特例,null 和 undefined 不是继承自 Object。

JS中关于构造函数、原型链、prototype、constructor、instanceof、__proto__属性的更多相关文章

  1. 面试题常考&必考之--js中的难点!!!原型链,原型(__proto__),原型对象(prototype)结合例子更易懂

    1>首先,我们先将函数对象认识清楚: 补充snow的另一种写法: var snow =function(){}; 2>其次:就是原型对象 每当我们定义一个函数对象的时候,这个对象中就会包含 ...

  2. js中使用使用原型(prototype)定义方法的好处

    经常在前端面试或是和其他同行沟通是,在谈到构造在JS定义构造函数的方法是最好使用原型的方式:将方法定义到构造方法的prototype上,这样的好处是,通过该构造函数生成的实例所拥有的方法都是指向一个函 ...

  3. JavaScript里的原型(prototype), 原型链,constructor属性,继承

    ① __proto__ 和 constructor 属性是 对象 所独有的. ② prototype 属性是 函数 所独有的. ** JS里函数也是引用类型的对象,所以函数也有 __proto__ 和 ...

  4. js中[object Object]与object.prototype.toString.call()

    最近在用node读取文件中的json数据后,用JSON.parse()转成了json,然后响应数据传给前端,发现输出值object对象时显示[object object],在这里我们来看一下他的具体意 ...

  5. 原型和原型链 prototype和proto的区别

    原型 原型是function对象下的属性,它定义了构造函数的共同祖先,也就是一个父子级的关系,子对象会继承父对象的方法和属性 prototype是函数下的属性,对象想要查看原型使用隐式属性__Prot ...

  6. 第20篇 js高级知识---深入原型链

    前面把js作用域和词法分析都说了下,今天把原型链说下,写这个文章费了点时间,因为这个东西有点抽象,想用语言表达出来不是很容易,我想写的文章不是简单的是官方的API的copy,而是对自己的知识探索和总结 ...

  7. JavaScript原型链:prototype与__proto__

    title: 'JavaScript原型链:prototype与__proto__' toc: false date: 2018-09-04 11:16:54 主要看了这一篇,讲解的很清晰,最主要的一 ...

  8. Js中关于构造函数,原型,原型链深入理解

    在 ES6之前,在Javascript不存在类(Class)的概念,javascript中不是基于类的,而是通过构造函数(constructor)和原型链(prototype chains)实现的.但 ...

  9. js原型和原型链理解 constructor 构造函数

    一.对象:普通对象   函数对象 二.构造函数特点:1.需要new实例化,内部使用this对象指向即将要生成的实例对象  2.首字母大写,用于区分普通函数 function Person(name){ ...

随机推荐

  1. Mac自带服务器的应用

    Mac OS下自带了apache,方便部署一些静态数据(html,css,js,xml,图片等数据) 一.系统默认路径 系统默认是隐藏apache安装目录的,但我们可以通过“命令行”或者“文件夹前往” ...

  2. C#-概念-类:类

    ylbtech-C#-概念-类:类 类(Class)是面向对象程序设计(OOP,Object-Oriented Programming)实现信息封装的基础.类是一种用户定义类型,也称类类型.每个类包含 ...

  3. RESTful再理解

    目录 目录 前言 RESTful的目的 REST的含义 表现层 状态转化 无状态协议HTTP 最后 前言 这是在经过一段时间的积累后,对RESTFul框架的再一次更深入的理解.希望能够将零散的知识点连 ...

  4. Learn Python the hard way, ex39 列表的操作

    #!/usr/bin/python #coding:utf-8 ten_things = "apples oranges crows telephone light sugar" ...

  5. vim-tabe多标签切换

    vim-tabe多标签切换 本文转载自https://www.cnblogs.com/liqiu/archive/2013/03/26/2981949.html 1.新建标签页 使用:tabe命令和文 ...

  6. 如何创建linux虚拟机

    一.安装配置linux虚拟机 第1步:运行"Vmware WorkStation",看到主页面. 第2步:创建新的虚拟机,新建虚拟机向导——典型(推荐). 第3步:选择稍后安装操作 ...

  7. SpringCloud异常

    Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could ...

  8. Meet in the middle算法总结 (附模板及SPOJ ABCDEF、BZOJ4800、POJ 1186、BZOJ 2679 题解)

    目录 Meet in the Middle 总结 1.算法模型 1.1 Meet in the Middle算法的适用范围 1.2Meet in the Middle的基本思想 1.3Meet in ...

  9. [暑假集训Day3T2]骑士问题

    标准的广搜. 采用队列保存形态,如果不会广搜的可以多看看PJ知识点.由于输入多组数据,每次标记数组要清空,每次队列元素也都要清空. 参考代码如下: #include<iostream> # ...

  10. JavaScript 各种遍历方式详解及总结

    JavaScript 各种遍历方式详解 在$.each中想要终止循环,但是它没有continue或者break这样的终止方式,所以尝试使用return来进行终止,但是发现并没有跳出循环.为了搞清楚js ...