Appearance
原型与原型链
原型的定义
- 每一个函数,对象,数组都有一个 prototype 属性,函数的实例对象没有 prototype 属性,但有__proto__属性并且指向函数的 prototype 属性
原型链的形成
- 实例对象在访问属性时,首先寻找本身的属性和方法,如果没有通过__proto__去找上层原型,如果上层也没有,继续沿着__proto__往上查找,形成原型链,最顶层的是 null
原型链指向
- 1、特殊的地方 Function.prototype===Function.proto
- 2、特殊的地方 Object.prototype.proto === null
- 3、函数的祖宗是 Function
- 4、Object 的祖宗是 null
- 5、[[Prototype]]和__proto__是等价的都是隐式原型
对象创建
TIP
Object 是 Function 的实例对象,所以Object.__proto__ === Function.prototype
js
// 方式一:
let obj = {};
// 方式二:
let obj1 = new Object();
Object.__proto__ === Function.prototype;
函数创建
TIP
所有的函数是 Function 的实例对象,所以fn.__proto__ === Function.prototype
js
// 方式一:
function fn(age) {
console.log(age);
}
// 方式二:
let fn1 = new Function("age", "console.log(age)");
函数 constructor
js
function fn() {}
let f = new fn();
// 当获取 f.constructor 时,其实 f 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 f 的原型也就是 f.prototype 中读取,正好原型中有该属性
f.constructor === fn;
// 构造函数原型的构造指向构造函数本身
fn.prototype.constructor === fn;
console.log([].constructor === Array); // true
console.log([].constructor === Object); // false
console.log(function () {}.constructor === Function); // true
原型链的优点
- 共享数据,减少空间占用,节省内存
- 可以实现继承
instanceof 类型检查
- 用于检测
左变量
是否在右变量
的原型链上 - 如果上层没找到会,一直往上,一直到原型链的顶端 null 为止
js
// 定义构造函数
function A() {}
function B() {}
var a = new A();
console.log(a instanceof A, a.__proto__ === A.prototype);
console.log(a instanceof B, a.__proto__ === B.prototype);
console.log(a instanceof Object, a.__proto__.__proto__ === Object.prototype);
instanceof 实现
js
function myInstanceof(left, right) {
// 显示的原型
let rightProto = right.prototype;
// 实例对象的隐士原型
let leftProto = left.__proto__;
while (true) {
// 如果找到返回true
if (leftProto === rightProto) {
return true;
}
// 到原型链顶端
if (leftProto === null) {
return false;
}
// 继续向上查找
leftProto = leftProto.__proto__;
}
}
function A() {}
function B() {}
var a = new A();
console.log(myInstanceof(a, A)); // true
console.log(myInstanceof(a, B)); // false
console.log(myInstanceof(a, Object)); // true
instanceof 可能异常
- 未声明的变量 instanceof 会报错
js
// 安全性不如typeof
aaaa instanceof Object;
// Uncaught SyntaxError: Unexpected identifier
typeof aaaa; // 'undefined'
hasOwnProperty
- 判断某个属性,是不是当前对象上的(不包含原型上的)
js
var obj = {
name: "hl",
};
console.log(obj.hasOwnProperty("name")); // true
console.log(obj.hasOwnProperty("age")); // flase
new 实现
- 1.创建一个新的对象
- 2.新对象的__proto__ 指向构造函数的 prototype
- 3.调用构造函数,并把 this 指向新创建的对象(通过 call)
- 4.如果返回值的类型是基本类型直接返回新的对象,否则返回返回值
js
function myNew(obj) {
let newObj = {};
newObj.__proto__ = obj.prototype;
const result = obj.call(newObj);
if (result instanceof Object) {
return result;
}
return newObj;
}
function fn2() {
return [];
}
var o = myNew(fn2);
console.log(o);
Object.create 实现原理
- 创建一个函数,让函数的 prototype 指向构造的原型,返回函数的实例
- Object.create(null) 构造一个非常干净的对象,不是继承 Object 原型上的属性
js
Object.create = function (proto) {
function F() {}
F.prototype = proto;
return new F();
};
let obj = { name: "hulei" };
let o = Object.create(obj);
o.age = 18;
console.log(obj); // {name:'hulei'}
console.log(o); // {age:'18'}
console.log(o === obj); // false
console.log(o.__proto__ === obj); // true
继承
- 优点:子类具有父类的能力,代码复用
- 缺点:如果继承的层级比较多,会造成代码混乱不好维护
构造继承
- 构造继承只能继承私有的属性,不能继承原型的属性
js
function f() {
this.name = "hulei";
this.arr = [0];
}
f.prototype.save = function () {
console.log(1);
};
function f1() {
this.age = "25";
f.apply(this);
}
var o1 = new f1();
console.log(o1); //{name: "hulei", age: "25"}
// o1.save(); //报错
原型继承
- 实例对象都指向同一个原型地址,如果直接更改原型地址,会影响所有的实例对象
js
function f() {
this.name = "hulei";
this.arr = [0];
}
function f2() {
this.age = 25;
}
f2.prototype = new f();
f2.prototype.constructor = f2; // 修正constructor
var o3 = new f2();
var o4 = new f2();
o3.arr.push(1);
console.log(o3.arr); // [0,1]
console.log(o4.arr); // [0,1]
组合式继承
- 构造和原型继承的结合体,既能继承私用的,也能继承原型的,数据完全隔离互不影响
js
function f() {
this.name = "hulei";
this.arr = [0];
}
function f2() {
f.apply(this);
this.age = 25;
}
f2.prototype = new f();
f2.prototype.constructor = f2; // 修正constructor
var o3 = new f2();
var o4 = new f2();
o3.arr.push(1);
console.log(o3.arr);
console.log(o4.arr);
多继承
- 多继承直接遍历函数原型,赋值,属性相同会后者会覆盖前者
js
function f4() {
f1.apply(this);
f2.apply(this);
}
for (const key in f1.prototype) {
f4.prototype[key] = f1.prototype[key];
}
for (const key in f2.prototype) {
f4.prototype[key] = f2.prototype[key];
}