原型----继承
原型链:访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。
继承:由于所有的对象的原型链都会找到Object.prototype,因此所有的对象都会有Object.prototype的方法。这就是所谓的“继承”。
设计原型、原型链的目的:为了让函数具有类的功能,函数都具有prototype属性。为了让实例化出来的对象能够访问到prototype上的属性和方法,实例对象的__proto__指向了类的prototype。
- JS中的函数可以作为函数使用,也可以作为类使用,作为类使用的函数实例化时需要使用new
- JS中万物(引用类型)皆对象,对象是属性的集合,表现为键值对的形式;
对象都是通过函数创建的,函数本身也是对象。
对象都是通过函数创建的
var obj = { a: 10, b: 20 };
var arr = [5, 'x', true];
//以上代码是快捷方式(语法糖),其实以上代码的本质是:
var obj = new Object();
obj.a = 10;
obj.b = 20;
var arr = new Array();
arr[0] = 5;
arr[1] = 'x';
arr[2] = true;
console.log(typeof (Object)); // function
console.log(typeof (Array)); // function
判断一个变量是不是对象的方法
值类型的判断用typeof,引用类型的判断用instanceof
console.log(typeof x); // undefined
console.log(typeof 10); // number
console.log(typeof "abc"); // string
console.log(typeof true); // boolean
console.log(typeof function () {}); //function
console.log(typeof [1, "a", true]); //object
console.log(typeof { a: 10, b: 20 }); //object
console.log(typeof null); //object
console.log(typeof new Number(10)); //object
重要
- 修改实例的 proto:仅影响该实例,不影响构造函数或其他实例。
- 修改构造函数的 prototype:影响所有未来由该构造函数创建的实例,但不影响已存在的实例。
归纳
每个函数都有一个属性叫做prototype(原型):这个prototype的属性值是一个对象,默认的只有一个叫做constructor的属性,指向这个函数本身。每个对象都有一个隐藏的属性——“proto”(隐式原型):对象的__proto__指向的是创建它的函数的prototype。即:fn.__proto__ === Fn.prototype,这里的"proto"成为“隐式原型”。- 所有的构造函数的隐式原型都等于 Function 的显示原型,函数都是由 Function 构造而来,Object 构造函数也不例外;
Object.__proto__ === Function.prototype。Function是被自身创建的,所以它的__proto__指向了自身的Prototype:Function.__proto__ === Function.prototype。 - 所有构造函数的显示原型的隐式原型,都等于 Object 的显示原型,Function 也不例外;
Function.prototype.__proto__ === Object.prototype - 原型链的最终指向是
Object的原型。特例:Object.prototype.__proto__ === null。 - ES6的class其实是函数类的一种语法糖,书写起来更清晰,但原理是一样的。
那么我们在实际应用中如何区分一个属性到底是基本的还是从原型中找到的呢?——hasOwnProperty,特别是在for…in…循环中,一定要注意。
实现一个instanceof
instanceof检查一个对象是不是某个类的实例,换句话说就是检查一个对象的的原型链上有没有这个类的prototype。
Instanceof的判断规则是:沿着A的__proto__这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。
instanceof表示的就是一种继承关系,或者原型链的结构。
知道了这个我们就可以自己实现一个了:
function myInstanceof(targetObj, targetClass) {
// 参数检查
if(!targetObj || !targetClass || !targetObj.__proto__ || !targetClass.prototype){
return false;
}
let current = targetObj;
while(current) { // 一直往原型链上面找
if(current.__proto__ === targetClass.prototype) {
return true; // 找到了返回true
}
current = current.__proto__;
}
return false; // 没找到返回false
}
// 用我们前面的继承实验下
function Parent() {}
function Child() {}
Child.prototype.__proto__ = Parent.prototype;
const obj = new Child();
console.log(myInstanceof(obj, Child) ); // true
console.log(myInstanceof(obj, Parent) ); // true
console.log(myInstanceof({}, Parent) ); // false
例子
| function Foo() {Foo.a = function() {console.log(1)}this.a = function() {console.log(2)}}Foo.prototype.a = function() {console.log(3)}Foo.a = function() {console.log(4)}Foo.a();let obj = new Foo();obj.a();Foo.a();输出顺序是 4 2 1 | function Foo() {Foo.a = function() {console.log(1)}this.a = function() {console.log(2)}}// 以上只是 Foo 的构建方法,没有产生实例,此刻也没有执行Foo.prototype.a = function() {console.log(3)}// 现在在 Foo 上挂载了原型方法 a ,方法输出值为 3Foo.a = function() {console.log(4)}// 现在在 Foo 上挂载了直接方法 a ,输出值为 4Foo.a();// 立刻执行了 Foo 上的 a 方法,也就是刚刚定义的,所以// # 输出 4let obj = new Foo();/* 这里调用了 Foo 的构建方法。Foo 的构建方法主要做了两件事:/*1. 将全局的 Foo 上的直接方法 a 替换为一个输出 1 的方法。/*2. 在新对象上挂载直接方法 a ,输出值为 2。obj.a();// 因为有直接方法 a ,不需要去访问原型链,所以使用的是构建方法里所定义的 this.a,// # 输出 2Foo.a();// 构建方法里已经替换了全局 Foo 上的 a 方法,所以// # 输出 1 |