目录
1.1 使用 new 创建对象 VS 使用 字面量 创建对象
1.2 使用 Object.create() 创建对象(使用 现有对象 提供 新建对象的 __proto__)
3.1 prototype 的作用 —— 存放实例对象共享的属性方法
3.2 constructor 属性真正的位置 —— 在 prototype 里
4.1 [[Prototype]] 属性 —— 不对外提供访问
4.3 对象.__proto__ —— 访问并修改 prototype 上的属性方法
4.3.1 [[Prototype]] VS __proto__ —— 区别在于能否被外部访问
4.3.2 Object.getPrototypeOf / Object.setPrototypeOf —— ES6中用于替换 __proto__
4.4 proto、prototype、constructor 的关系
何谓原型链?
当某个对象上的方法或属性不存在时,会在它的原型上去查找;
如还不存在,就会去它原型的原型上查找;
这样形成了一条链路,即原型链;
1. JavaScript 创建对象的三种方式
有人说 JavaScript 一切皆对象,是错误的!原始值就不是
1.1 使用 new 创建对象 VS 使用 字面量 创建对象
先来看看 使用 new 创建对象:
var person = new Object();
person.name = "TeaMeow";
person.sayHello = function () {
console.log("Hello!");
};
再来看看 使用 字面量 创建对象:
var person = { } 等价于 var person = new Object()
var person = {
name: 'TeaMeow',
sayHello: function () {
console.log('Hello!')
}
}
1.2 使用 Object.create() 创建对象(使用 现有对象 提供 新建对象的 __proto__)
Object.create() —— 使用 现有对象 提供 新建对象的 __proto__
假设 me 是新建对象,那么 me.__proto__=== person
// 现有对象 person
var person = {
name: 'TeaMeow',
sayHello: function () {
console.log('Hello!')
}
}
// 将 现有对象person 作为 新建对象me 的 __proto__
var me = Object.create(person);
me.name = 'miaomiao';
2. 构造函数是什么,有什么用呢
2.1 构造函数是什么
任何函数都可以作为构造函数,之所以有构造函数与普通函数之分,是因为功能不同
构造函数通过 new 实例化对象,它可以为 初始化对象 添加 属性 和 方法
function Person() {}
var person1 = new Person();
var person2 = new Person();
person1.constructor === Person; // true
person2.constructor === Person; // true
上面的 person1 和 person2 都是通过 Person 函数实例化出来的
Person 函数就是当前 person1 和 person2 的构造函数
对象上的 constructor 属性,可以指明对象的构造函数是啥
2.2 对象、构造函数、创建函数对象方法 的关系
上面的 Person 函数,本身也是一个对象,那么这个对象是如何实例化出来的呢? 它的 constructor 又指向谁呢?这就涉及到 创建函数对象 的方法 Function
对象、构造函数、函数对象的关系示例:
- person1 对象,由 Person 函数实例化得到;person1.constructor === Person 函数
- Person 函数,由 JavaScript 内置函数 Function 函数实例化得到;Person.constructor === Function 函数
- 而 Function 本身是构造函数,它的 constructor 是本身,Function.constructor === Function
2.3 constructor 属性在哪里
打印上面的例子中的 person1,查看 constructor 属性,打印如下:
会发现看不见 constructor 属性,为什么呢?
明明可以访问到啊(person1.constructor === Person),这个问题的答案解释在 3.2
3. prototype 是什么,有什么用呢
3.1 prototype 的作用 —— 存放实例对象共享的属性方法
给构造函数设置一个 prototype 属性【它其实是个对象】,用于存放 所有实例对象 共享 的属性和方法,可以有效解决 内存浪费 问题
function Person() {}
Person.prototype.hairColor = "back";
Person.prototype.sayHello = function () {
console.log("Hello!");
};
var person1 = new Person();
var person2 = new Person();
所有函数都可以是构造函数,因此,所有函数都具有 prototype 属性,包括 Function() 函数
3.2 constructor 属性真正的位置 —— 在 prototype 里
- 如果实例打印时,每个实例都给赋值了一个 constructor 属性,会导致内存浪费
- 因为他们的 constructor 可能是一样的,不如放在 prototype 里,以节约内存
4. __proto__ 是什么,有什么用呢
4.1 [[Prototype]] 属性 —— 不对外提供访问
person1.[[Prototype]] === Person.prototype
person1.[[Prototype]].hairColor === Person.prototype.hairColor
[[Prototype]] 属性,是内部隐藏属性,不对外提供访问
因此,通过 对象.[[Prototype]] 无法查看和修改原型 prototype 上的属性和方法
4.2 constructor 不可信
举个栗子~~
function Person() {}
Person.prototype.hairColor = "black";
Person.prototype.sayHello = function () {
console.log("Hello!");
};
var person1 = new Person();
var person2 = new Person();
====================================================
function Dog() {}
person1.constructor = Dog;
Dog.prototype.hairColor = "red"; // 在 Dog.prototype 上定义 hairColor 属性
console.log(person1.constructor); // Dog
console.log(person2.constructor); // Person
console.log(person1.hairColor); // black
hairColor 是 black 而不是 red,所以通过 constructor 获取实例的构造函数,然后获取共享属性(hairColor)的方法不可取
4.3 对象.__proto__ —— 访问并修改 prototype 上的属性方法
后来许多浏览器厂商实现了 __proto__属性 ,(最开始是火狐浏览器提供的)
__proto__指向了 [[Prototype]],可以通过 对象.__proto__ 得到对象 原型对象 prototype 上的属性和方法,同样,也可以去修改
4.3.1 [[Prototype]] VS __proto__ —— 区别在于能否被外部访问
对象.[[Prototype]] = 创建自己的构造函数内部的 prototype(原型对象),无法外部访问
对象.__proto__ = 创建自己的构造函数内部的 prototype(原型对象),可以外部访问
对象.__proto__ = 对象.[[Prototype]]
Function.__proto__ === Function.prototype
4.3.2 Object.getPrototypeOf / Object.setPrototypeOf —— ES6中用于替换 __proto__
__proto__在 ES6 以前不是 JavaScript 标准,是浏览器提供的
由于越来越流行,运用广泛,在 ES6 规范中,被标准化为传统功能,以确保 Web 浏览器的兼容性
现在已经不推荐使用 __proto__ 了,推荐使用 Object.getPrototypeOf / Object.setPrototypeOf
4.4 proto、prototype、constructor 的关系
JavaScript 中,对象分为两种:
- 普通对象
- 函数对象
__proto__(指向构造函数的 prototype)是对象独有的;函数创建的对象.__proto__ === 该函数.prototype
constructor(指向对象的构造函数) 是对象独有的; 构造函数.prototype.constructor === 该构造函数本身
prototype(包含所有实例提供共享的属性和方法)是函数独有的;
在 JavaScript 中,函数也是对象,所以函数也拥有 __proto__ 和 constructor 属性
console.log(Function.prototype === Function.__proto__); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
5. 原型模式的基本概念 / 原型链查找原理
5.1 原型模式的基本概念
JavaScript 的数据类型包括:基础类型 + 引用类型
由于没有类(ES6 引入了 class,但只是语法糖)的概念,如何将所有对象联系起来,是个问题,于是有了原型和原型链的概念:
- 所有的引用类型(数组、对象、函数)都有一个 __proto__属性(隐式原型属性),本质是个对象
- 所有的函数,都有一个 prototype(显式原型属性),存放了其实例可共享的属性和方法。
- 对象的 __proto__ 等于实例这个对象的构造函数的 prototype
所谓的原型链上去查找,其实就是通过对象的__proto__去查找(这个链可以理解为用__proto__去连接)
- 先查看实例上是否具有该属性,即 对象.属性 是否有值
- 如果 1 中没找到,就去实例的原型对象(__proto__)找有没有该属性,即 对象.__proto__ 是否有值
- 如果 2 中没找到,就取 对象.__proto__.__proto__ 找有没有该属性,一直通过 __proto__ 链接下去,直到终点 null
5.2 原型链是什么?和作用域有什么区别?
访问一个对象上的属性时,如果该对象内部不存在这个属性,那么就会去它__proto__
属性所指向的对象(原型对象)上查找。如果原型对象依旧不存在这个属性,那么就会去其原型的__proto__
属性所指向的原型对象上去查找。以此类推,直到找到 null
,而这个查找的线路,也就构成了我们常说的原型链
原型链和作用域的区别:
- 原型链是查找对象上的属性
- 作用域链是查找上下文中的变量
5.3 JavaScript 内置函数的原型链
Array.prototype // []
Array.__proto__ === Function.prototype
Array.__proto__.__proto__ === Object.prototype
Array.__proto__.__proto__.__proto__ === null
Objecy.prototype // {}
Objecy.__proto__ // Function.prototype
Function.prototype // ƒ ()
Function.__proto__ // Function.prototype
String.prototype // 空字符串
String.__proto__ // Function.prototype
6. instanceof
6.1 instanceof 是什么
instanceof 可以判断:一个对象的原型链上,是否包含该构造函数的原型(也就是说 —— 对象是否为该构造函数的实例)
console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Function instanceof Object); // true
console.log(function() {} instanceof Function); // true
。
6.2 instanceof 与 typeof 的区别
instanceof —— 判断对象是否为某个构造函数的实例
typeof —— 判断一个变量的数据类型
typeof 可以用来判断 number、undefined、symbol、string、function、boolean、object 这七种数据类型【特殊情况:typeof null === 'object'】
6.3 手写 instanceof
function instanceOf(obj, fn) {
// 获取对象的 __proto__
let proto = obj.__proto__;
if (proto) {
// 如果对象的__proto__ 和 构造函数的 prototype 一致
if (proto === fn.prototype) {
// 证明 当前对象 是 构造函数 的实例
return true;
} else {
// 递归调用,顺着原型链往上找
return instanceOf(proto, fn);
}
// 如果没有 __proto__ 证明走到了原型链的尽头 null
} else {
return false;
}
}
// 测试
function Dog() {}
let dog = new Dog();
console.log(instanceOf(dog, Dog), instanceOf(dog, Object)); // true true
7. new 一个对象发生了什么
7.1 new 对象的基本步骤 / 构造函数有返回值的理解
基本步骤:
- 修改原型 —— 创建一个对象,该对象的原型,指向构造函数的原型
- 修改 this —— 调用该构造函数,构造函数的 this,指向新生成的对象
- 确定返回值 —— 判断构造函数是否有返回值,如果有返回值,且返回值是一个对象或一个方法,则返回该值;否则返回新生成的对象
关于构造函数有返回值的理解:
function Dog(name) {
this.name = name;
return { test: 1 };
}
let obj = new Dog("aaa");
console.log(obj); // { test: 1 }
7.2 手写 new
利用 Object.create() 生成一个对象,新生成对象的原型,是构造函数的原型
function selfNew(fn, ...args) {
// 创建一个 instance 对象,该对象的原型是 fn.prototype
let instance = Object.create(fn.prototype);
// 调用构造函数,使用 apply,将 this 指向新生成的对象
let res = fn.apply(instance, args);
// 如果fn函数有返回值,并且返回值是一个对象或方法,则返回该对象,否则返回新生成的 instance 对象
return typeof res === "object" || typeof res === "function" ? res : instance;
}