简介:
仅从设计模式的角度讲,如果我们想要创建一个对象,一种方法是先指定它的类型,然后通过这个类来创建对象,例如传统的面向对象编程语言 "C++"、"Java" 等;另一种方式是,我们不需要关心对象的具体类型,而是找到一个对象,然后通过克隆来创建一个一模一样的对象,就像所有吸血鬼故事必然有一个吸血鬼祖先一样,在 "Javascript" 世界中 "Object" 对象就是这个吸血鬼祖先,所有其他对象都是继承自 "Object" 对象来的,如果 A 对象继承自 B 对象,那么 B 对象就是 A 对象的原型。
在基于原型的对象系统中,至少包含以下规则:
(1) 一切皆对象
(2) 要创建一个对象不是通过实例化类,而是找到一个对象作为原型并克隆它
(3) 对象会记住它的原型
(4) 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型 (原型链)
创建对象:
在 JS 中有三种创建对象的方式:通过对象字面量、通过构造函数、通过 Object.create() 函数。
//示例一:通过对象字面量创建对象 var boy = { name: ‘Bob‘, sex: ‘male‘ }
//示例二:通过构造函数创建对象 function Person(name, sex) { this.name = name; this.sex = sex; } var boy = new Person(‘Bob‘, ‘male‘);
//示例三:通过 Object.create() 函数创建对象 var boy1 = { name: ‘Bob‘, sex: ‘male‘ } var boy2 = Object.create(boy1);
一切皆对象
事实上,在 JS 中并非一切皆对象,这只是一种笼统的说法,由于 JS 引入了两种数据类型:基本类型 ( Undefind、 Null、 Boolean、 Number 和 String ) 和对象类型 ( Object 、Function),对象类型是对象自然不必多说,问题在于基本类型是对象吗?我们先上字符串类型来说明一下:
var str = ‘Make life better‘; console.log(str.length); //输出 16
按理说 "str" 变量只是一个字符串,但是它却使用了对象才有的 "length" 属性,输出了字符串的长度,因此这里我们有理由把字符串类型看成对象,称为 "包装对象"。这个对象是临时的,也就是说只有在读取它的属性的时候 JS 才会把这个字符串通过 new String() 方式创建成一个字符串对象,一旦引用结束这个对象就被销毁了,换句话说 "包装对象" 的属性是 "只能读,不能写" 的。同理 "Boolean" 和 "Number" 在读取属性的时候也可以通过自己的构造函数来创建自己的一个包装对象,并像对象一样引用各自的属性。
其次,"null" 表示 "空值",对 "null" 执行 "typeof" 操作,输出结果为 "Object",所以我们也可以把 "null" 看成一个对象,称为 "空对象"
最后,"undefind" 表示 "未定义",当我们对变量只声明没有初始化时,输出 "undefind",或者引用一个不存在的属性时,输出也为 "undefind",对 "undefind" 执行 "typeof" 操作的输出结果为 "undefind",这么说来 "undefind" 其实并不属于对象范畴
要创建一个对象不是通过实例化类,而是找到一个对象作为原型并克隆它
在 JS 中,"Object.prototype" 是所有对象的原型,我们并不需要关心克隆的细节,因为这是引擎内部负责实现的。我们所需要做的只是显式地调用 var obj1 = {}; 或者 var obj2 = new Object(),此时,引擎内部会从 "Object.prototype" 上面克隆一个对象出来,作为新对象的原型。
示例一:
var obj1 = {}; var obj2 = new Object(); console.log(Object.getPrototypeOf(obj1) === Object.prototype); //输出true console.log(obj2.__proto__ === Object.prototype); //输出true
每个对象都具有 "__proto__"(前后两个下划线) 属性,它指向该对象的原型,但是它只是一个内部属性,而不是一个正式的对外 API,原则上是不能访问的,这是由于很多浏览器的支持,才把这个属性暴露出来了。在ES5中使用 "Object.getPrototypeOf()" 获取一个对象的原型,在ES6中可以使用 "Object.setPrototypeOf()" 设置一个对象的原型。因此,在这里两者的作用都是一样的,都是获取对象的原型,并且它们的原型都是 "Object.prototype"。
只有函数才有 "prototype" 属性,例如 "Object.prototype",对于函数而言,"__proto__" 属性指向它的原型, "prototype" 属性则是通过这个函数构造出来的对象的原型,可以理解为这样一条原型链,"__proto__" 总是指向原型链的顶端,而函数恰好可以延长原型链,于是它将自己 "prototype" 属性指向的对象压入原型链的顶端,自然它构造出来的对象的 "__proto__" 属性就指向了它自己的 "prototype"。
示例二:
function Person(name, sex) { this.name = name; this.sex = sex; } var boy = new Person(‘Bob‘, ‘male‘); console.log(Object.getPrototypeOf(Person) === Function.prototype); //true console.log(Object.getPrototypeOf(boy) === Person.prototype); //true
Person 函数继承自 Function 对象,如果这么写就很直观了:
var Person = new Function(‘name‘, ‘sex‘, ‘this.name = name;this.sex = sex;‘);
因此 Person 函数的原型指向 Function.prototype,boy 对象是通过 Person 函数构造而来的,因此它的原型指向 Person.prototype。
对象会记住它的原型
上面已经提到,JS 给所有对象提供了一个 "__proto__" 属性,用于访问它的原型
如果对象无法响应某个请求,它会把这个请求委托给它自己的原型
示例:
function A() {} A.prototype = { name: ‘better‘ }; var a = new A(); a.name = ‘bett‘; function B() {} B.prototype = a; var b = new B(); console.log(b.name); //输出bett
b 对象本身没有 "name" 属性,于是在它的原型,即构造函数的 "prototype" 中去找,a 对象有 "name" 属性,于是停止上溯,输出bett。
原文地址:https://www.cnblogs.com/blog-cxj2017522/p/8992138.html