第一次觉得“原型链和继承”看着是那么那么顺眼...

目录:
1、原型&原型链注意
2、js中继承的六种方式
3、new关键字
4、instance of
5、Object.create
6、复制实现继承:浅拷贝、深拷贝
7、继承理解DOM
8、创造对象的四种方式

1、原型&原型链注意

提到原型说对象(目录8),每一个对象都有一个__proto__隐式原型属性,指向构造改对象的构造函数的原型。
而函数比较特殊,它具备prototype这样一个指针属性,指向包含所有实例共享的属性和方法的对象,称为原型对象;原型对象有一个constructor属性,指向该函数。
因此具备:Object.\_\_proto\_\_ === Function.prototype;Function.\_\_proto\_\_ === Function.prototype。Object是对象的构造函数,对象的构造函数均指向“Function.prototype”,因为Function本身是一个构造函数,为不产生混乱,就将两个联系到一起。
几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。
function Person(){}
let p1 === new Person()
let obj === {}

p1.__proto__ === Person.prototype

Person.__proto__ === Function.prototype
Person.prototype
Person.prototype.__proto__ === Object.prototype
Person.prototype.constructor === Person

Function.__proto__ === Function.prototype
Function.prototype
Function.prototype.__proto__ === Object.prototype
Function.prototype.constructor === Function

obj.__proto__ === Object.prototype

Object.__proto__ === Function.prototype
Object.prototype
Object.prototype.__proto__ === null
Object.prototype.constructor === Object

JavaScript 并没有其他基于类的语言所定义的“方法”。在 JavaScript 里,任何函数都可以添加到对象上作为对象的属性。函数的继承与其他的属性继承没有差别,包括上面的“属性遮蔽”(这种情况相当于其他语言的方法重写)。
当继承的函数被调用时,this 指向的是当前继承的对象,而不是继承的函数所在的原型对象。

2、js中继承的六种方式

1、原型链继承

特点:通过原型链继承,引用类型会因为引用的特点变成公用属性;且无法在继承的时候传递参数;可以使用instanceof、isPropertyOf检测出来

function Company(){
    this.address = '深圳总部';
    this.projects = ['games']
}
Company.prototype.showAddress = function(){
    console.log("address为" + this.address);
}

function SonCompany(address){
    this.address = address;
}

SonCompany.prototype = new Company();
// 将SonCompany.prototype.__proto__由指向Object.prototype转为指向Company.prototype,继而SonCompany.prototype.__proto__.__proto__ === Object.prototype

var sonC1 = new SonCompany('广州分部');
var sonC2 = new SonCompany('上海分部');
sonC1.projects.push('movies');
console.log(sonC2.projects); // ["games", "movies"]
console.log(sonC1);
/*
SonCompany 
    address: "广州分部"
    __proto__: Company
        address: "深圳总部"
        projects: (2) ["games", "movies"]
        __proto__:
            showAddress: ƒ ()
            constructor: ƒ Company()
            __proto__: Object

而没有使用prototype继承的时候,sonC1为:
SonCompany {address: "广州分部"}
    address: "广州分部"
    __proto__:
        constructor: ƒ SonCompany(address)
        __proto__: Object
*/

/*
此时一般还是需要将sonCompany的constructor换回来的,即应该在SonCompany.prototype = new Company();之后操作SonCompany.prototype.constructor = SonCompany;
这样操作之后,sonC1打印出来如下:
SonCompany {address: "广州分部"}
    address: "广州分部"
    __proto__: Company
        address: "深圳总部"
        constructor: ƒ SonCompany(address)
        projects: ["games"]
        __proto__:
            showAddress: ƒ ()
            constructor: ƒ Company()
            __proto__: Object
*/

2、构造函数继承

思想:在子类型构造函数的内部call、apply调用父类构造函数
特点:解决了原型链“引用类型私有属性变公有”、“不可传递参数”两个问题;
但是没有使用new生成一个父类实例的时候,方法写在prototype上无法使用,所有方法需要在函数中定义。这样的话就导致无法复用,每次构建实例的时候都会在每一个实例中保留方法函数,造成内容浪费,无法同步更新。写在prototype上,就只有一份,更新可以同步更新。

function Company (address) {
  this.address = address;
  this.projects = ['games'];
  this.getAddress = function () {
    return this.address;
  }
}

Company.prototype.showAddress = function(){ console.log(this.address) }

// 子类
function SonCompany (address) {
  // 继承了Company,同时还传递了参数
  Company.call(this, address);
  // 实例属性
  this.staff = 20;
}

var com1 = new Company('深圳总部');
com1.getAddress(); // 深圳总部
com1.showAddress(); // 深圳总部
var sonC1 = new SonCompany('广州分部');
sonC1.getAddress(); // 广州分部
sonC1.projects.push('movies');
var sonC2 = new SonCompany('上海分部');
console.log(sonC2.projects) // ["games"]
sonC2.getAddress(); // 上海分部
sonC2.showAddress(); // Uncaught TypeError: sonC2.showAddress is not a function

/*
sonC1的打印: getAddress成为sonC1的一个函数
SonCompany
    address: "sonC1Address"
    staff: 20
    getAddress: ƒ ()
    projects: (2) ["games", "movies"]
    __proto__:
        constructor: ƒ SonCompany(address)
        __proto__: Object

com1的打印:可以查到showName()
Company
    address: "SuperAddress"
    getAddress: ƒ ()
    projects: ["games"]
    __proto__:
        showAddress: ƒ ()
        constructor: ƒ Company(address)
        __proto__: Object
*/

3、组合继承(1、2组合)

思想:将上述两种结合,发挥各自优势;是目前广泛使用的继承方式;但是对父类构造函数调用了两次;

function Company (address, staff) {
  this.address = address;
  this.staffs = staff;
  this.projects = ['games'];
  this.getAddress = function () {
    return this.address;
  }
}

Company.prototype.showAddress = function(){ console.log(this.address) }

// 子类
function SonCompany (address, staff) {
  // 继承了Company,同时还传递了参数
  Company.call(this, address);
  // 实例属性
  this.staffs = staff;
}
SonCompany.prototype = new Company();
// 将SonCompany.prototype.__proto__由指向Object.prototype转为指向Company.prototype,继而SonCompany.prototype.__proto__.__proto__ === Object.prototype
SonCompany.prototype.constructor = SonCompany;
// 将构造函数转回来,因为上一步操作之后:SonCompany.prototype.constructor === Company;转之后即可在SonCompany的prototype上正常操作 
SonCompany.prototype.getStaff = function(){
    return this.staffs
}

var com1 = new Company('深圳总部', 100);
com1.getAddress(); // 深圳总部
com1.showAddress(); // 深圳总部
var sonC1 = new SonCompany('广州分部', 20);
sonC1.getAddress(); // 广州分部
sonC1.projects.push('movies'); // ['games', 'movies']
var sonC2 = new SonCompany('上海分部', 40);
console.log(sonC2.projects) // ['games']
sonC2.getAddress(); // 上海分部
sonC2.showAddress(); // 上海分部
sonC2.getStaff(); // 40

/*
SonCompany
    address: "广州分部"
    getAddress: ƒ ()
    projects: ["games"]
    staffs: 20
    __proto__: Company
        address: undefined
        constructor: ƒ SonCompany(address, staff)
        getAddress: ƒ ()
        getStaff: ƒ ()
        projects: ["games"]
        staffs: undefined
        __proto__:
            showAddress: ƒ ()
            constructor: ƒ Company(address, staff)
            __proto__: Object

*/

4、原型式继承

思想:不适用严格意义上的构造函数,借助原型基于已有对象创建新对象,同时不必创建自定义类型;
一般是仅仅模拟一个对象的时候使用; es5新增了一个方法Object.create(prototype, descripter)接收两个参数:
prototype(必选),用作新对象的原型对象
descripter(可选),为新对象定义额外属性的对象
在传入一个参数的情况下,Object.create()与前面写的object()方法的行为相同。
使用一个函数对传入其中的对象执行一次浅复制,复制到的副本还需要进一步改造;引用类型属性还是会被共享

function objectCopy(o){
    function F(){} // 创建一个临时构造函数
    F.prototype = o; // 将传入的对象作为构造函数的原型
    return new F(); // 返回临时类型的新实例
}
var company = {
    address: "深圳分部",
    projects: ['games'],
    getAddress: function(){
        console.log(this.address);
    }
}
var c1 = objectCopy(company);
var c2 = objectCopy(company);
c1.projects.push('movies');
console.log(c2.projects); // ["games", "movies"]
c1.getAddress(); // "深圳分部"

5、寄生式继承

思想:创建一个仅用于封装继承过程的函数,改函数内部以某种形式来做增强对象,最后返回对象。

function objectCopy(o){
    function F(){}
    F.prototype = o;
    return new F();
}

function createAnother (original) {
  var clone = objectCopy(original) // 通过调用函数创建一个新对象
  clone.sayHi = function () { // 以某种方式来增强这个对象
    alert("hi")
  }
  return clone // 返回这个对象
}

var company = {
    address: "深圳分部",
    projects: ['games'],
    getAddress: function(){
        console.log(this.address);
    }
}
var c1 = createAnother(company);
var c2 = createAnother(company);
c1.projects.push('movies');
console.log(c2.projects); // ["games", "movies"]
c1.sayHi(); // hi
/*
c1打印
F {sayHi: ƒ}
    sayHi: ƒ ()
    __proto__:
        address: "深圳分部"
        getAddress: ƒ ()
        projects: (2) ["games", "movies"]
        __proto__: Object
*/

6、组合寄生式继承(3、5)

思想:使用构造函数继承属性,原型链混成形式继承方法;只调用一次构造函数
被认为最理想的继承方式;

function objectCopy(o){
    function F(){}
    F.prototype = o;
    return new F();
}

function inheritPrototype(s, f){
    var prototype = objectCopy(f.prototype); // 创建对象
    prototype.constructor = s; // 增强对象
    s.prototype = prototype; // 指定对象
}

function Company (address, staff) {
  this.address = address;
  this.staffs = staff;
  this.projects = ['games'];
  this.getAddress = function () {
    return this.address;
  }
}

Company.prototype.showAddress = function(){ console.log(this.address) }

// 子类
function SonCompany (address, staff) {
  // 继承了Company,同时还传递了参数
  Company.call(this, address);
  // 实例属性
  this.staffs = staff;
}

inheritPrototype(SonCompany, Company); // 继承

SonCompany.prototype.getStaff = function(){
    return this.staffs
}

var com1 = new Company('深圳总部', 100);
com1.getAddress();
com1.showAddress();
var sonC1 = new SonCompany('广州分部', 20);
sonC1.getAddress();
sonC1.projects.push('movies');
var sonC2 = new SonCompany('上海分部', 40);
console.log(sonC2.projects)
sonC2.getAddress();
sonC2.showAddress();
sonC2.getStaff();

/*
sonC1打印如下
SonCompany
    address: "广州分部"
    getAddress: ƒ ()
    projects: (2) ["games", "movies"]
    staffs: 20
    __proto__: Company
        constructor: ƒ SonCompany(address, staff)
        getStaff: ƒ ()
        __proto__:
            showAddress: ƒ ()
            constructor: ƒ Company(address, staff)
            __proto__: Object
*/

7、es6的class实现继承

底层使用的是组合寄生式继承;写法简单易懂,如下:

class Company{
    constructor(address){
        this.address = address;
        this.projects = ['games']
    }
    getAddress(){
        console.log(this.address)
    }
}

class SonCompany extends Company{
    constructor(address, staffs){
        super(address) // 继承父类实例属性和prototype上的方法
        this.staffs = staffs
    }
    getStaffs(){
        console.log(this.staffs)
    }
}

var sonC1 = new SonCompany('广州分部', 40);
var sonC2 = new SonCompany('上海分部', 20);
sonC1.getAddress();  // 广州分部
sonC1.getStaffs();  // 40
sonC1.projects.push('movies');
sonC2.getAddress(); // 上海分部
sonC2.getStaffs(); // 20
sonC2.projects; // ['games']

3、new关键字

new做了四件事er:

1、创建一个新对象
2、将__proto__属性指向构造器函数的prototype
3、将this指向新创建对象,使用新创建的对象执行构造函数
4、返回新建对象

function _new(fn){
	return function(){
		let obj = {  // 创建新obj
			__proto__: fn.prototype  // 原型链 - 由指向Object.prototype转为fn.prototype
		}
		// 执行构造函数,传递参数
		fn.call(obj, ...arguments); // 构造函数改变参数的this指向obj,apply也是OK的,不过参数问题这样方便;所以根据这个,fn中的this指的是new的对象
		// 返回obj
		return obj
	}
}

function A(a){
	this.a = a
}
var obj = _new(A)('aa');  // {a: 'aa}
// obj.__proto__ === A.prototype   true
// obj.constructor === A.constructor   true
// obj2.__proto__.__proto__ === Function.prototype  false
// obj.__proto__.__proto__ === Object.prototype   true
// A.__proto__ === Function.prototype

函数科里化: fun里return一个fun

4、instance of

会根据原型链一直找下去

function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
        var O = R.prototype;// 取 R 的显示原型
        L = L.__proto__;// 取 L 的隐式原型
        while (true) { 
        if (L === null) //L是Object.prototype
            return false; 
        if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true 
            return true; 
        L = L.__proto__; 
    } 
}

对比之:typeof
typeof是判断基本类型的,基本类型有7种: Undefined, null, Boolean, Number, String, bigint, symbol(es6),还能判断出function 然后其他都是object(null是object)

判断类型:

function myTypeof(obj){
  return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}

5、Object.create

判断第一个参数是否符合继承的特点,即是否为具有__proto__属性,是否为对象,函数也是对象;
判断第二个参数是否符合条件,即不为undefined;将对象赋值给第一个参数

var _create = function (proto, propertyObject = undefined) {
    // 先判断proto是否符合条件
    if (typeof proto !== 'object' && typeof proto !== 'function') {
        throw new TypeError('Object prototype may only be an Object: ' + proto);
    }
    /*
    else if (proto === null) {
        throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
    }
    */

    if (propertyObject === null) {
        throw 'TypeError'
    } else {
        function Fn() { }
        Fn.prototype = proto
        const obj = new Fn()
        if (propertyObject !== undefined) {
            Object.defineProperties(obj, propertyObject)
        }
        if (proto === null) {
            // 创建一个没有原型对象的对象,Object.create(null)
            obj.__proto__ = null
        }
        return obj
    }
}


var  proto = {name:'allen'};
var  p = _create(proto,{age:{value:21}});
p.hasOwnProperty('age');//true
p.hasOwnProperty('name');//false

Object.create(null) vs {}
前者就是一个单纯的{},不具备properties的属性;{} 相当于 Object.create(Object.ptototype),存在n.toString(),且可以用for…in循环

6、复制实现继承:浅拷贝、深拷贝

浅拷贝可以使用Object.assign()或展开运算符...,例如:

// 浅拷贝
let shallowCopy = Object.assign({}, originalObject);
// 或
let shallowCopy = {...originalObject};

深拷贝可以使用JSON.parse()JSON.stringify(),但它无法拷贝函数等非JSON数据类型。更完整的深拷贝可以使用递归实现,例如:

// 深拷贝
function deepCopy(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  let copy = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepCopy(obj[key]);
    }
  }
  return copy;
}

这样可以创建原对象的完整拷贝,包括对象内部的所有嵌套对象。

7、继承理解DOM

DOM上是具备原型链的;
文档对象模型 (DOM) 将 web 页面与到脚本或编程语言连接起来。通常是指 JavaScript,但将 HTML、SVG 或 XML 文档建模为对象并不是 JavaScript 语言的一部分。DOM模型用一个逻辑树来表示一个文档,树的每个分支的终点都是一个节点(node),每个节点都包含着对象(objects)。DOM的方法(methods)让你可以用特定方式操作这个树,用这些方法你可以改变文档的结构、样式或者内容。节点可以关联上事件处理器,一旦某一事件被触发了,那些事件处理器就会被执行。

8、创建对象的4种方式

来看看使用不同方法来创建对象:
1.语法结构

var o = {a: 1};
// o ---> Object.prototype ---> null ; o 这个对象继承了 Object.prototype 上面的所有属性

var a = [1, 2, 3]
// a ---> Array.prototype ---> Object.prototype ---> null;数组都继承于 Array.prototype 

function f() { return true}
// f ---> Function.prototype ---> Object.prototype ---> null;函数都继承于 Function.prototype;(Function.prototype 中包含 call, bind等方法)

2.构造器(即普通函数),即使用new操作符

function testF(a){ this.a =  a }
testF.prototype = {
	funA: function(){
		console.log(this.a)
	}
}
var t = new testF('aaa');
// t.__proto__ === testF.prototype

3.使用Object.create创建对象(ES5方法),新对象的原型就是调用create时传入的第一个参数

var a = {a: 1}; 
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype

手写:
function createObject(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}
// 使用方法
var newObj = createObject(existingObject);

4.使用class关键字(ES6引入的语法糖,虽然有class,extends的概念,单js还是基于原型)

class A { static geta(){ console.log( 'A' ); } }
class B extends A { static getb(){ console.log( 'B' ); } }
B.geta(); // A
B.getb(); // B 
要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从 Object.prototype 继承的 hasOwnProperty 方法。(在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。)遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。Object.keys()也是跟 hasOwnProperty 一样的效果;(注意:检查属性是否为 undefined 是不能够检查其是否存在的。该属性可能已存在,但其值恰好被设置成了 undefined。)
原型链是可扩展的,但是不建议随意扩展【原生对象】的原型,这样会影响到很多继承原生对象的使用;

扩展原型链大概有一下4种:new、Object.create、Object.setPrototypeOf、proto 越靠后越要求版本等,还是建议new

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值