面向对象 —— 逻辑迁移更加灵活、代码复用性高、高度的模块化
构造函数 原型
实现 js 的面向对象。一个能面向对象的语言必须要做到:能判定一个实例的类型。原型的存在避免了类型的丢失。
new 不支持对象共享属性和方法,原型支持
函数拥有 prototype,对象拥有 __proto__
原型链
作用:让某一个构造函数实例化的所有对象可以找到公共的属性和方法
每个对象都有一个原型,并从原型上继承属性和方法。原型本身也是一个对象,也有自己的原型,形成一个链式结构
把原型串联起来,最顶端是 null
查找规则
对象本身 ==> 构造函数 ==> 对象原型 ==> 构造函数原型 ==> 对象上一层原型
1 2 3 4 5 6 7 8 9 10 11 12 13 function Fun ( ) { this .a = "查找第二步" ; } Fun .prototype .a = "查找第四步" ;let obj = new Fun ();obj.a = "查找第一步" ; obj.__proto__ .a = "查找第三步" ; Object .prototype .a = "查找第五步" ;console .log (obj.a );
object.__proto__
vs object.prototype
js 中每个对象都有一个原型对象 prototype (null 除外)
object.__proto__
为浏览器提供用于获取 prototype 的非标准方法,只读不可赋值,并且修改该属性会造成严重的性能问题,因为 JavaScript 通过隐藏类优化了很多原有的对象结构,直接修改 __proto__
会直接破坏现有已经优化的结构,触发 V8 重构该对象的隐藏类
es5 提供了获取原型的标准方法 Object.getPrototypeOf
每个构造函数都有一个 prototype 属性,指向实例对象的原型对象
object.prototype 可以为实例对象添加原型的属性和方法
js 对象
本质上并不是基于类,而是基于构造函数 + 原型链
创建 js 对象
核心:obj.__proto__
= Object.prototype
Object.create() 1 2 3 4 Object .create 创建了一个对象;let p = Object .create (q) -> p.__proto__ = q;
var obj = {} 1 2 3 4 5 6 7 let p = Object .create ({});let p = Object .create (obj);p.__proto__ .__proto__ = Object .prototype ;
new Function()
创建了一个对象;
该对象的原型,指向了这个 Function(构造函数)的 prototype;
将当前实例对象赋给了内部 this
该对象实现了这个构造函数的方法;
根据一些特定情况返回对象
如果没有返回值,则返回创建的对象;
如果有返回值,是一个对象,则返回该对象;
如果有返回值,不是个对象,则返回创建的对象;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function newObj (Father ) { if (typeof Father !== "function" ) { throw new Error ( "new operator function the frist param must be a function!" ); } var obj = Object .create (Father .prototype ); var result = Father .apply (obj, Array .prototype .slice .call (arguments , 1 )); return result && typeof result === "object" && result !== null ? result : obj; }
1 2 3 4 5 6 7 8 9 10 function Person (name, age ) { this .name = name; this .age = age; } var p = new Person ();p.__proto__ === Person .prototype ; Person .prototype .constructor === Person ;p.constructor === Person ;
constructor + prototype 1 2 3 4 5 6 7 8 function Course (teacher ) { this .teacher = teacher; this .leader = "小可" ; this .startCourse = function (name ) { return `开始${name} 课` ; }; } const course = new Course ("数学" );
Course 本质就是构造函数
函数体内使用的 this,指向所要生成的实例
生成对象用 new 来进行实例化
可以做初始化传参
如果构造函数不初始化,可以使用吗? - 无法使用 如果项目中需要使用,通常(不被外界感知)如何解决?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function Course ( ) { const _isClass = this instanceof Course ; if (!_isClass) { return new Course (); } this .teacher = "胜利哥" ; this .leader = "小可" ; this .startCourse = function (name ) { return `开始${name} 课` ; }; } const course = Course ();
启发:如果编写底层的 api 代码时,尽量做到不需要让外部感知内部类型
constructor 是什么? 1 2 3 4 5 function Course (teacher, leader ) { this .teacher = teacher; this .leader = leader; } const course = new Course ("云隐" , "小可" );
每个对象在创建时,会自动拥有一个构造函数属性 constructor
constructor 继承自原型对象,指向了构造函数的引用
构造函数的问题 1 2 3 4 5 6 7 8 9 10 11 function Course (name ) { this .teacher = "胜利" ; this .leader = "小可" ; this .startCourse = function (name ) { return `开始${name} 课` ; }; } const course1 = new Course ("es6" );const course2 = new Course ("OOP" );
解决方案:挂在原型对象上 1 2 3 function Course ( ) {}const course1 = new Course ();const course2 = new Course ();
构造函数: 用来初始化创建对象的函数 - Course
自动给构造函数赋予一个属性 prototype,该属性等于实例对象的原型对象
实例对象:course1 是实例对象,根据原型对象创建出来的实例
每个对象中都有一个 __proto__
每个实例对象都有一个 constructor
constructor 有继承而来,并指向当前的构造函数
原型对象:Course.prototype
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Course ( ) {}Course .prototype .teacher = "胜利" ;const course1 = new Course ();const course2 = new Course ();function Course ( ) { this .teacher = "胜利" ; this .leader = "小可" ; } Course .prototype .startCourse = function (name ) { return `开始${name} 课` ; }; const course1 = new Course ("es6" );const course2 = new Course ("OOP" );
继承
在原型对象的所有属性和方法,都能被实例所共享
1 2 3 4 5 6 7 8 9 10 11 12 13 function Game ( ) { this .name = "lol" ; } Game .prototype .getName = function ( ) { return this .name ; }; function LOL ( ) {}LOL .prototype = new Game ();LOL .prototype .constructor = LOL ;const game = new LOL ();
原型链继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function Game ( ) { this .name = "lol" ; this .skin = ["s" ]; } Game .prototype .getName = function ( ) { return this .name ; }; function LOL ( ) {}LOL .prototype = new Game ();LOL .prototype .constructor = LOL ;const game1 = new LOL ();const game2 = new LOL ();game1.skin .push ("ss" );
缺点:
父类属性一旦赋值给子类的原型属性,此时属性属于子类的共享属性了,如果有属性是引用类型,一旦某个实例,修改了这个属性,所有的都会被改
实例化子类时,无法向父类做传参
构造函数继承
经典继承:在子类构造函数内部调用父类构造函数(把 Parent 上的属性和方法,添加到 Child 上面去,而不是都存在在原型对象上,防止被实例共享)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function Game (arg ) { this .name = "lol" ; this .skin = ["s" ]; } Game .prototype .getName = function ( ) { return this .name ; }; function LOL (arg ) { Game .call (this , arg); } const game3 = new LOL ("arg" );
缺点:
属性或者方法向被继承的话,只能在构造函数中定义
如果方法在构造函数中定义了,那么每次创建实例都会创建一遍方法
组合继承
解决原型链上的共享方法无法被读取继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function Game (arg ) { this .name = "lol" ; this .skin = ["s" ]; } Game .prototype .getName = function ( ) { return this .name ; }; function LOL (arg ) { Game .call (this , arg); } LOL .prototype = new Game ();LOL .prototype .constructor = LOL ;const game3 = new LOL ();
缺点:
无论何种场景,都会调用两次父类构造函数。
初始化子类原型时
子类调用函数内部 call 父类的时候
寄生组合继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function Game (arg ) { this .name = "lol" ; this .skin = ["s" ]; } Game .prototype .getName = function ( ) { return this .name ; }; function LOL (arg ) { Game .call (this , arg); } LOL .prototype = Object .create (Game .prototype );LOL .prototype .constructor = LOL ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function Game (arg ) { 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 ;const game3 = new LOL ();
es6 继承 1 2 3 4 5 6 7 8 9 10 11 12 class A {}class B extends A { constructor ( ) { super (); } }