new
首先要搞清楚什么是new,new是javascript中的一种操作符,作用是通过构造函数去创建一个实例对象。
function Foo(name) {
this.name = name;
}
console.log("new Foo('mm')的类型:",typeof new Foo('mm')); // object
console.log("Foo的类型:",typeof Foo); // function
new出的结果是一个对象,它都做了些什么,中间的过程是什么?
new所做及操作过程,总结一下就是:一建、二连、三改、四返回。
一建:创建一个新的对象
二连:将当前新对象的原型__proto__指向构造函数的原型对象;
三改:通过构造函数的相关方法,将this的原本指向window,调整为当前对象
四返回:将对象返回
有些大佬给出了new的过程分析,记录一下地址:
JS 的 new 是个啥? - 徐航宇 - 博客园
JS中的new操作符 - 见嘉于世 - 博客园
原型链
原型本质上就是一个对象。实例对象拥有__proto__属性,构造函数拥有prototype原型属性,它们两个的属性都会指向同一个对象,这个对象就是原型对象,它们都拥有着与之相应的相同属性。
那么原型链呢,原型链就是以原型对象为核心,向外扩展的类似链条一样,由对象与构造函数所构成的集合。
原型链的形成/过程:
当查找某个对象或某个构造函数的某个属性时,先会查找自身,当自身不存在该属性,会根据自身的__proto__或prototype属性,向它的原型查找;
如果它的原型中也不存在,那么会重复之前的步骤,再向原型的原型中查找,直到最终Object.prototype.proto返回null,则停止查找。
函数的原型属性 —— prototype
对象的原型属性 —— proto
构造函数new的隐式原型 —— __proto__
函数也是对象,也拥有proto属性;默认情况下,所有原型对象会自动获得constructor属性,包含指向prototype属性所在的函数
很多的文章中都给到了图注,我只是记录一下自己的理解,可能我本身对与它的理解也还是有点不对的地方,对于更详细的,感兴趣的话可以看一下
javascript——原型与原型链 - 雅昕 - 博客园
说说原型(prototype)、原型链和原型继承 - 知乎
原型链以及继承的几种方式 - 酸菜鱼i - 博客园
到这不得不再记录一下原型链的继承。继承是指一个对象直接使用另外一个对象的属性和方法。
原型链继承
实现原理:
- 原型链继承让子类的prototype指向父类的实例;
function Super(name) {
this.name = name
this.a = [1, 2, 3, 4]
}
Super.prototype.say = function () {
return 22
}
function Sub() { }
Sub.prototype = new Super() // 原型链继承就是让子类的prototype指向父类的实例
Sub.prototype.constructor = Sub; // 再让prototype的constructor重新指向它关联的构造函数。用来补全原型(prototype),每个prototype原型都有一个constructor属性,指向它关联的构造函数
const sub1 = new Sub();
const sub2 = new Sub();
console.log(sub1.a) // [1, 2, 3, 4]
console.log(sub1.say()) // 22
缺陷:
1.引用值共享问题;也就是说父类属性一旦复制给子类的原型属性,此时属性属于子类共享属性 --继承者的实例间篡改;
- 例如:sub1.a.push(5) console.log(sub1.a) ==> [1, 2, 3, 4, 5] console.log(sub2.a) ==> [1, 2, 3, 4, 5]
2.实例化子类时 无法向父类传参
- 例如:const sub1 = new Sub('王者'); console.log(sub1.name) ==》 undefined
如何解决原型链的问题呢?
构造函数继承
实现原理:
- 在子类的构造函数内部,借用call() 或者 apply() 方法,调用超类型的构造函数
- 通过call或者aply方法,将父对象的构造函数绑定在子对象上
function Super(name) {
this.name = name
this.a = [1, 2, 3, 4]
}
Super.prototype.say = function () {
return 22
}
function Sub(arg) {
//使用call或者aply方法,将父对象的构造函数绑定在子对象上;
Super.call(this, arg)
}
const sub1 = new Sub('5');
const sub2 = new Sub();
sub1.a.push(5)
console.log(sub1.name) // '5'
console.log(sub1.a) // [1,2,3,4,5]
console.log(sub2.a)// [1,2,3,4]
缺陷:
- 没有办法拿到原型上的方法
例如 :console.log(sub1.say()) ==> Uncaught TypeError: sub1.say is not a function
如何解决原型链上的共享方法就无法被读取继承?
组合继承
实现原理:
- 指的是将原型链和借用构造函数的技术组合到一块,从而返回二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承
function Super(name) {
this.name = name
this.a = [1, 2, 3, 4]
}
Super.prototype.say = function () {
return 22
}
function Sub(arg) {
//使用call或者aply方法,将父对象的构造函数绑定在子对象上;
Super.call(this, arg)
}
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
const sub1 = new Sub('5');
const sub2 = new Sub('8');
console.log(sub1.name) // '5'
console.log(sub2.name) // '8'
console.log(sub1.say()) // 22
缺陷:
- 父类的构造函数都会被执行两次
例如: new Super() 执行一次, Super.call(this, arg)执行一次
如何解决组合继承消耗内容资源问题?
寄生组合继承
基本思路:
不必为了指定子类型的原型而调用超类型的构造函数,因为我们所需要的只是超类型原型的一个副本而已;
function Super(name) {
this.name = name
this.a = [1, 2, 3, 4]
}
Super.prototype.say = function () {
return 'Super'
}
function Sub(arg) {
//使用call或者aply方法,将父对象的构造函数绑定在子对象上;
Super.call(this, arg)
}
// 兼容低版本es5以下版本 不存在 Object.create 方法
if (!Object.create) {
Object.create = function (proto) {
function F() { };
F.prototype = proto;
return new F() // 这里我们要返回的是一个实例对象;
}
}
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
const sub1 = new Sub('5');
const sub2 = new Sub('8');
console.log(sub1.name) // '5'
console.log(sub2.name) // '8'
console.log(sub1.say()) // Super
提高: 多重继承
function Game(name) {
this.name = 'LOL';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
function Store() {
this.shop = 'steam';
}
Store.prototype.getPlatform = function() {
return this.shop;
}
function LOL(arg) {
Game.call(this, arg);
Store.call(this, arg);
}
LOL.prototype = Object.create(Game.prototype);
Object.assign(LOL.prototype, Store.prototype);
LOL.prototype.constructor = LOL;
// LOL继承了两个类