IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 深入聊一下javascript原型链,继承,面向对象,es5,es6,this,设计模式 -> 正文阅读

[JavaScript知识库]深入聊一下javascript原型链,继承,面向对象,es5,es6,this,设计模式

javascript是一门垃圾语言

javascript设计之初是没有考虑很多的,别管这门语言的作者什么背景,在怎么牛逼的人也不能在一周之内把语言设计的很好的。

就拿最基本的数据类型来说,javascript对引用类似唯一提供的就是对象,且实现有问题(详见对象的遍历顺序)。

我很喜欢java,它是一门标准的面对对象语言,一切皆对象,即使有int,float等关键字的遗留,但那是为了照顾老人,还是可以通过封装类来达到对象的转化。

javascript原型链是个啥

var obj={
	a:1
};
var obj1={
	b:2
}
Object.setPrototypeOf(obj1,obj);

这样,obj就在obj1的原型链上了。
然后obj1.__proto__===obj 了。这就是原型链。
obj在obj1的链上,obj1可以访问obj的属性,这就叫做原型属性。

javascript的继承

比较奇怪的是js面向对象的方式。
在java里,一个对象要创建出来需要new,new是一个很重量级的操作,需要JVM进行一系列操作,它不像简单的堆栈运算,毕竟涉及到对象。
而非常困惑我当初作为javaer的就是js里的对象。
我明白JSON和js对象的爱恨情仇,详见json RFC草案标准,所以我认为js不会把一个使用这么频繁的东西搞得很复杂,开销很大。果真,js是没有对对象做很复杂的操作的。
那,作为一个承载数据的数据结构,js如何实现OOP范式呢?
这就不得不从js的版本号说起。
现在的js全部都是从ecmascript开始流行起来的,
es2015是没有class关键字的,因此无法引入面向对象,但是自从es2015,js就已经很火很火了,怎么办?难道一大堆英雄好汉?不,js还有一个保留字—new。于是,可能在ES39某次会议上,大家决定可以使用函数来作为对象使用。于是,函数这种数据结构,你就不得不重视起来,因为它等价于class。底层是用它封装的语法糖形成的class关键字,但是这个底层是开发浏览器的底层,作为一名业务层程序员还是欣赏不到的。所以,怎么写?

function Student(_name,_age){
	//这里相当于构造器
	//这里的this是指这个函数形成的实例化对象
	this.name=_name;
	this.age=_age;
}
Student.prototype.name;
Student.prototype.age;
Student.prototype.run(){
    //这里的this相当于实例化对象。
	console.log(this.name+":run");
}
Student.play(){
	console.log("学生都爱玩");
}
Student(); // 相当于执行普通的函数,this指向(window/undefined)
let stu = new Student();//相等于面向对象

简单来说,es5为了可以面向对象,因此使用了new保留字。函数也是一个对象啊,函数里面的内容如何区分它是成员属性还是成员方法?就引入了一个prototype指针。这个指针指向的区域是这个函数实例化后形成的成员属性和方法指向的区域。也因此这个实例就可以拥有这些属性和方法。静态属性就直接放到类名.引用,这个很简单,没啥好说的。而new Student()才可以让这个函数真正成为一个面向对象,形成一个对象。形成的这个对象stu里面的属性放到了原型链上,我们之前说过,所有原型链上的属性和方法叫做原型属性和方法,本身不属于这个对象。也因此stu.__proto__Student.prototype有一块儿缓冲的地方做交流和沟通。stu是普通的JSON可以沟通的对象,而它和普通对象有点不同的是直接创建的对象的原型链上直接就指向Object的原型了。这点有点类似于java里面的直接以Object new对象。js里面创建对象就相当于一个语法糖吧。稍等,让我们说明白java里面的对象为什么和js里面的对象不是一个重量级的。每次java new对象,都会在线程变量里面保存一个独一的类的副本,往这个父本上填写对象。假如java new的这个对象是一个数据结构。java需要这个数据结构的很多实例,那么每个对象都能拿到独一无二的数据结构API方法的一套父本,而我们创建对象更多的考虑的是怎么用里面的数据,毕竟pojo为代表的java对象是大量使用的嘛。也因此,js做出了一个完全不同的改变。将对象保存、承载数据的理念发挥到极致,但是为了引入对于实例属性和方法的描述,给出了一个原型指针作为属性。(这个原型指针 就是也是一个对象,是一个相当于这个类的静态方法和属性动态使用的一种策略)。这样,每次new对象,相当于简单的为这个对象填入了对象属性,原型属性则通过一个指针来进行链接,拿到这个原型下所有API。这样多好,就拿到了这个数据结构的所有内容,而且还是保存在一份内存中。对象又可以简单的只是承载数据,就太灵活了。。。
而对于构造器的使用,因为构造器很像这个函数本身,这个函数一大些,就相当于这个构造器了,所以Student.prototype.constructor===Student
这样写不太好理解,那就列一个等式:
stu.__proto__===Student.prototype
stu.__proto__.constructor===这个类的构造器===只执行一次的函数,按照es5的构造,这个代码片段可以放在那里就很清晰了,就是new Student时的那个函数里面的内容。所以
stu.__proto__.constructor===Student
根据等式:
stu.__proto__.constructor===Student===Student.prototype.constructor,
再联想到Java里构造器的名字和类名相同,Student就是一个构造器,其放了一个指针在对象里面,使得对象 可以直接通过 对象名.constructor的方式来访问到这个构造器。而这个构造器是这个类/函数对象,这个类/函数对象里面又有一个prototype属性,这个prototype属性又会有constructor属性,…如此就形成了一个循环,其实就是在浏览器手动点击一下就相当于顺着这个指针找内容,不点肯定不是递归的,因此也不会产生很大的存储浪费。所以对象深复制时,当拿到对象的这个属性的key为constructor时,就代表了遍历了这个对象,此时因为拿到的是自己的对象属性,即使对象可以访问其原型属性,它也肯定是进入了其原型链这个对象(__proto__)里面去进行拷贝,此时的source是这个原型链对象,如果遇到constructor对象,因为constructor对象下面是这个函数对象,函数对象下面又会有prototype属性,prototype属性里面又有constructor,赶快跳出来,于是就简单的将此时得到的这个键为constructor的这个对象的值–>一块引用地,的constructor(原型对象)或者直接将他自己对象属性给出去,然后完成克隆。
再说一下js里面的包装类型。
js也是有堆栈的,这样写和这样写完全不一样:

var n1=1;
var n2=new Number(1);

一个只是在栈中存储了一个值而已。
另一个只是在堆中存储了一个对象,并且把这个对象的地址赋值给了栈中的一个引用而已。
但是为什么只要将1赋值给一个引用地址,这个引用地址就可以找到Number类对应的API呢?很简单,在解析n1的数据类型时(js是弱类型,需要运行时动态解析数据类型,这也是snippets不太友好的原因之一),可以将其映射到对应的Number类。这本身也是一种引用,这种引用就和指针直来直去的不太一样了,不过也是引用的一种。
所以,n1和n2也就完全不一样了。
而且nullundefined没有所谓的引用,是因为在Js中,并没有对应的包装类,因此他们就只能以第一种形态存储在栈中而变成一种值。
但是typeof null为什么是对象?很简单,也是用了这个技术,将内存直接映射到了Object上。因为Number最终也是要通过原型链找到Object的。只不过Number好像也是es5面向对象继承过来的,因此Number->Function->Object这样一个顺序。但是为什么undefined没有,而是undefiend呢?我认为这是一个10亿美元的bug造成的,就是引入了null这个设计,好多程序引入了这个值造成了臭名昭著的空指针异常,但是我喜欢的Rust没有这个缺陷,很舒服。所以当初最初的时候可能就只有undefined这个判断类型,这个null也是在不得已情况下加上去的。

继承中的this指向除了执行父类构造器中的this是父类外,其他甚至于父类的方法都是指向的是子类对象。
也因此js里面的很多设计模式根本没办法做,es6太弱,没有接口,es5写面向对象还需要重构。就很恶心。所以用ts做。

增量理解

图:
在这里插入图片描述
在这里插入图片描述
继承的理解
在这里插入图片描述

es5实现面相对象

function Fu(_a) {
  this.a = _a;
  console.log("i am fu");
}
Object.defineProperties(Fu.prototype, {
  hello: {
    value() {
      console.log('hello');
    }
  }
});
function Zi(_a) {
  this.super(_a);
  console.log('I am zi');
}
Object.defineProperties(Zi.prototype, {
  sayHello: {
    value() {
      this.superFn("hello");
      console.log('say hello');
    }
  }
});
function extendz(subClass, supClass) {
  Object.setPrototypeOf(subClass.prototype, supClass.prototype);
  Object.defineProperties(subClass.prototype, {
    super: {
      value: function () {
        supClass.apply(this, arguments);
      }
    },
    superFn: {
      value: function (fnName) {
        return supClass.prototype[fnName].apply(this, Array.from(arguments).slice(1));
      }
    }
  });
}
extendz(Zi, Fu);
var zi = new Zi(2);
console.log(zi);
zi.hello();

修改原型写法:

function Fu(_a) {
  this.a = _a;
  console.log("i am fu");
}
Object.defineProperties(Fu.prototype, {
  hello: {
    value() {
      console.log('hello');
    }
  }
});
function Zi(_a) {
  this.super(_a);
  console.log('I am zi');
}
Object.defineProperties(Zi.prototype, {
  sayHello: {
    value() {
      this.superFn("hello");
      console.log('say hello');
    }
  }
});
function extendz(subClass, supClass) {
  Object.setPrototypeOf(subClass.prototype, supClass.prototype);
  Object.defineProperties(subClass.prototype, {
    super: {
      value: function () {
        supClass.apply(this, arguments);
      }
    },
    superFn: {
      value: function (fnName) {
        return supClass.prototype[fnName].apply(this, Array.from(arguments).slice(1));
      }
    }
  });
}
extendz(Zi, Fu);
var zi = new Zi(2);
console.log(zi);
zi.hello();

注:用js实现有诸多不便,super是一个关键字,是浏览器的内核实现的,所以这里只是模仿其写法,不必纠结完全一样,思想会了就可以了。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-12 17:23:13  更:2022-03-12 17:24:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:28:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码