文章目录
# why?
让面向对象的 更加舒服(语法糖)
# hello world
class User {
constructor(name) {
this.name = name ;
}
getName() {
return this.name;
}
}
let h = new User('hello');
let w = new User('world');
console.log(h.getName(), w.getName())
# 类的 内部工作机制:原型操作
## 类(class) 和函数(function ) :属性 - 对比
属性是非常的相似
class User {}
function Hd() {} ;
console.dir(User) ;
console.log(User.prototype.constructor === User)
console.dir(Hd) ;
console.log(Hd.prototype.constructor === Hd)
## 类(class) 和函数(function ) :方法 - 对比
类,自动把 方法放在了 原型中 <mark>方便</mark>
class User {
show() {}
}
function Hd() {} ;
Hd.prototype.show = function() {} ;
console.dir(User)
console.dir(Hd)
## 类(class) 和函数(function ) :方法(特征) - 对比
class 的 方法 默认了 是不可遍历的
不需要像 函数那样,要 defineProperty 来改
class User {
show() {}
}
function Admin() {}
Admin.prototype.show = function() {} ;
console.log(
JSON.stringify(
Object.getOwnPropertyDescriptor(User.prototype, 'show'),
null,
2
)
)
console.log(
JSON.stringify(
Object.getOwnPropertyDescriptor(Admin.prototype, 'show'),
null,
2
)
)
## 类(class)和函数(function):构造函数 - 对比
class User {
constructor(name) {
this.name=name;
}
}
function Hd(name) {
this.name=name;
} ;
let u = new User('aa') ;
let h = new Hd('bb') ;
console.dir(u)
console.dir(h)
文章目录
# 严格模式 "use strict"
## 函数(function)
'use strict'
function show() {
console.log(this) ;
}
function User() {}
User.prototype.show = function() {
function test() {
console.log(this);
}
test() ; // window or undefined(严格模式)
console.log(this); // user
show(); // window or undefined(严格模式)
}
let u = new User() ;
u.show();
## 类(class)
可见(下图),(class内部)默认使用 严格模式
function show() {
console.log(this);
}
class Hd{
show(){
function test() {
console.log(this);
}
test(); // undefined
console.log(this); // Hd
show() ; // window or undefined(严格模式)
}
}
let h = new Hd() ;
h.show();
文章目录
# 静态属性
分配给构造函数的属性,我们称为静态属性
function Web(url) {
this.url = url ;
}
Web.url = 'hdcms.com'; //静态属性
let hd = new Web('houdunren.com') ;
console.log(hd);
console.dir(Web) ;
## 类(class)定义 静态属性(static)
下面的写法不是 静态
class Request {
host = 'https://www.houdunren.com';
}
let obj = new Request() ;
obj.host = 'http://baidu.com' ; // 实体类修改,不影响class类
console.log(obj);
let obj2 = new Request() ;
console.log(obj2); // 不受影响
属性写在了 实例类内部
两个实体类的 属性互不影响
这时候, 需要用 <mark>static</mark>
class Request {
static host = 'https://www.houdunren.com';
}
let obj = new Request() ;
console.log(obj);
console.dir(Request);
## 无需实体类,直接调用
class Request {
static host = 'https://www.houdunren.com';
api(url) {
return `${Request.host}/${url}`;
}
}
let obj = new Request() ;
console.log(obj.api("article"))
文章目录
# 静态方法
和静态属性类似,
分配给构造函数的方法,我们称为静态方法
function User() {}
User.prototype.show = function() { // 原型链上的 show
console.log('prototype show');
};
User.show = function() {
console.log('static show');// 静态方法
}
let hd = new User();
hd.show();
User.show();
console.log(hd);
## 类(class)定义 静态方法(static)
class User {
show() {
console.log('prototype show');
}
static show() { // 静态方法
console.log("static show");
}
}
new User().show();
User.show() ;
console.dir(User) ;
## 静态方法应用:new this()
通过静态方法,能给类的创建过程弄个工厂
class User {
constructor(name , age) {
this.name = name ;
this.age = age ;
}
static create(... args) {
return new this(... args) ;
}
}
let xj = User.create('aaa', 111) ;
console.log(xj)
# 案例 - 课程管理类
const data = [
{name: 'js', price: 100} ,
{name: 'mysql', price: 212} ,
{name: 'vue.js', price: 99}
];
class Lesson {
constructor(data) {
this.modal = data ;
}
static createBatch(data) { // 批量创建
// map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
// return data.map((item) => { // 可以简化成下面一行
// return new Lesson(item) ;
// });
return data.map(item => new Lesson(item));
}
get price() { // 访问器
return this.modal.price;
}
get name() {
return this.modal.name ;
}
static maxPrice(data) { // 取最大
return data.sort((a,b) => {
return b.price-a.price;
})[0];
}
static totalPrice(data) { // 统计
return data.reduce((previous, current) => {
return previous+current.price;
}, 0);
}
}
let lessons = Lesson.createBatch(data) ;
console.log(lessons);
console.log(Lesson.maxPrice(lessons).price); // 取price 最大
console.log(Lesson.totalPrice(lessons)) ; // 统计 总价格
# 类中使用访问器
访问器是大量使用的特性
可以在获取/设置属性之前 做一些操作
比如,下面的有效性判断
class Request {
constructor(host) {
this.data = {} ; // 不加, host 那里报错
this.host = host ;
}
set host(url) {
if(!/^https?:\/\//i.test(url)) { // i 忽略大小写
throw new Error('地址错误') ;
}
this.data.host = url ;
}
get host() {
return this.data['host'] ;
}
}
// let hd = new Request('www.baidu.com'); // 这是不行的
let hd = new Request('http://www.baidu.com')
console.log(hd.host)
console.log(hd)
# 保护属性 - Symbol
高内聚,不希望外部随便修改我们属性
const PROTECT = Symbol();
class Common {
constructor() {
this[PROTECT] = {};
this.host = 'https://www.baidu.com';
}
set host(url) {
if (!/^https?:\/\//i.test(url)) {s
throw new Error("异常地址");
}
this[PROTECT].host = url;
}
get host() {
return this[PROTECT].host;
}
}
class User extends Common {
constructor(name) {
super();
this[PROTECT].name = name ;
}
get name() {
return this[PROTECT].name;
}
}
let u = new User('bilibili');
console.log(u);
// u.host = 'www.baidu.com' ; // 无法插入
u.host = 'https://www.bilibili.com';
console.log(u.host) ;
console.log(u.name) ;
console.log(u);
# 保护属性 - WeakMap
为什么
Symbol
可以实现功能,但是不优雅。 😛
怎么用?
把属性数据 放在第三方的 内存里面
必须获得 第三方内存 才能修改数据
为什么是 WeakMap
因为WeakMap
是弱引用,随着 键的 其他强引用消失,对应的 value 值也会释放,避免了内存泄漏
## 设置单个值
const host = new WeakMap() ;
class User {
constructor(name) {
this.name = name ;
host.set(this, 'https://www.baidu.com') ;
}
set host(url) {
if(!/^https?:\/\//i.test(url)) {
throw new Error("网址异常")
}
host.set(this, url) ;
}
get host() {
return host.get(this);
}
}
let hd = new User('后盾热') ;
hd.host = 'http://www.e-handtai.com'
console.log(hd) ;
console.log(hd.host)
## 设置多个值的情况
const protecteds = new WeakMap() ;
class User {
constructor(name) {
this.name = name ;
protecteds.set(this,{
host: 'https://www.baidu.com',
port: 80
}) ;
}
set host(url) {
if(!/^https?:\/\//i.test(url)) {
throw new Error("网址异常")
}
protecteds.get(this).host = url ;
}
get host() {
return protecteds.get(this).host;
}
get port() {
return protecteds.get(this).port;
}
}
let hd = new User('后盾热') ;
hd.host = 'http://www.e-handtai.com'
console.log(hd) ;
console.log(hd.host, hd.port) ;
# 私有属性/方法 #
## 使用
普通定义 属性 、方法(方法要用函数)
在方法前加 #
即为 私有
class User{
#id = 1 ;
}
let u = new User() ;
u.#id ;
class User{
#fun = function() {} ;
}
let u = new User() ;
u.#fun();
## 修改
只能在 内部 进行修改
class User{
#id = 1 ;
set id(id) {
this.#id = id ;
}
get id() {
return this.#id ;
}
}
let u = new User() ;
u.id = 2 ;
console.log(u.id)
console.log(u)
class User{
constructor() {
this.#func() ;
}
#func = function() {
console.log("private function") ;
}
}
let u = new User() ;
## 无法继承
class Common {
#func = function() {} ;
}
class User extends Common{
constructor() {
this.#func() ;
}
}
let u = new User() ;
class Common {
#id = 1 ;
}
class User extends Common{
constructor() {
this.#id;
}
}
let u = new User()
文章目录
# 继承
视频:https://www.bilibili.com/video/av79397327?p=17
## super 的必要性
先看一种情况:
<mark>不用 super 调用 上级方法</mark>
let user = {
name: 'user' ,
show() {
console.log(this.name)
}
}
let old = {
__proto__ : user ,
name: 'old',
show() {
this.__proto__.show.call(this);
}
}
old.show() ;
这样看着就麻烦。
更重要的是,当要调用上上级的方法时,是无法做到的 (看下面)
let common = {
name: 'common' ,
show() {
console.log(this.name)
}
}
let user = {
__proto__: common ,
name: 'user' ,
show() {
this.__proto__.show.call(this)
}
}
let old = {
__proto__ : user ,
name: 'old',
show() {
this.__proto__.show.call(this);
}
}
old.show() ;
为什么?
<mark>因为,User show 中的 this.__proto__
理应指向 common 。但实际指回了user</mark>
所以就导致了 循环调用
用 super 会变得很简单
let common = {
name: 'common' ,
show() {
console.log(this.name)
}
}
let user = {
__proto__: common ,
name: 'user' ,
show() {
super.show();
}
}
let old = {
__proto__ : user ,
name: 'old',
show() {
super.show();
}
}
old.show() ;
## super 小案例
class Common {
sum() {
return this.data.reduce((previous, current) => {
return previous+=current.price;
}, 0);
}
}
class Controller extends Common{
}
class Lesson extends Controller {
constructor(data) {
super() ;
this.data = data ;
}
info() {
return {
totalPrice: super.sum() ,
data: this.data
}
}
}
let data = [
{name: 'js', price: 100} ,
{name: 'mysql', price: 211} ,
{name: 'vue.js', price: 89} ,
] ;
let hd = new Lesson(data) ;
console.log(hd.info())
## 方法重写
class Common {
constructor(data) {
this.data = data ;
}
sum() {
return this.data.reduce((previous, current) => {
return previous+=current.price;
}, 0);
}
getByKey(key) {
return this.data.filter(item => {
return item.name.includes(key) ;
});
}
}
class Lesson extends Common {
constructor(data) {
super(data) ;
}
getByKey(key) {
return super.getByKey(key).map((item) => {
return item.name;
});
}
}
let data = [
{name: 'js', price: 100} ,
{name: 'mysql', price: 211} ,
{name: 'vue.js', price: 89} ,
] ;
let com = new Common(data) ;
let hd = new Lesson(data) ;
console.log('parent',com.getByKey('js'))
console.log('sun',hd.getByKey('js'))
## 静态继承(构造方法的对象的继承)和实例继承原理
class 的实现就原型(语法糖)
那么 静态继承 (下图) 对应的 原型方法时怎么实现的呢?(下下图)
class User {
static site = 'houdnren.com'
static show () {
console.log(this.site) ;
}
}
class Admin extends User {} ;
Admin.show() ;
console.dir(Admin);
可以看到,(上图),静态继承的实现只是把 东西放在了 __proto__
(父类的原型方法)里面
对应着下图
function User() {} // 既是个函数,也是个对象
User.site = '后盾人'
User.show = function() {
console.log(this.site)
} ;
function Admin() {} ;
Admin.__proto__ = User ; // 继承
Admin.show();
console.dir(Admin)
所以说,class(语法糖) 的格式 实现的功能是一样的,但是看着更加简洁把?
注意:
也正是因为这种特性(静态方法写在 <mark>构造方法的对象内部</mark>)
而实例调用方法,是从原型链里面找(不经过<mark>构造方法的对象内部</mark>)
所以,实例是没办法调用静态(<mark>构造方法的对象内部</mark>)方法的
java 是可以的
class One { static show() { console.log('one show static') } } let one = new One() ; one.show();
所以跟 java 不一样,javascript 的<mark>静态继承(构造方法的对象的继承)</mark> 跟 <mark>实例对象的继承</mark> 是分开的(是没联系的)
只是 class 这种语法糖结构,把两步一起做了(让我们觉得看起来是有联系的)
<mark>实例对象的继承</mark> 如下
function One() {} ; One.prototype.show = function() { console.log("one show") } function Two () {} ; Two.prototype.__proto__ = One.prototype; // 这是非标准写法,浏览器厂商开发的(这里图个方便,就这样写了) let t = new Two() ; t.show() ;
class (语法糖)形式,同时做了以下两步
class User {}
class Admin extends User{}; // (语法糖)形式,同时做了以下两步
console.log(Admin.__proto__ == User)
console.log(Admin.prototype.__proto__ == User.prototype)
## instanceof
instanceof
判断<mark>后者的原型</mark>是否在<mark>前者对象的原型链</mark>上(实例继承)
function One() {} ;
function Two () {} ;
Two.prototype.__proto__ = One.prototype;
let t = new Two() ;
console.log(t instanceof Two) ;
console.log(t instanceof One) ;
//能用方法实现吗??
function checkPrototype(obj, constructor) {
if(obj.__proto__==null) return false ;
if(obj.__proto__==constructor.prototype) return true ;
return checkPrototype(obj.__proto__ , constructor) ; // 递归判断
}
console.log(checkPrototype(t, Two))
console.log(checkPrototype(t, One))
## isPrototypeOf
function A() {} ;
A.prototype.show = function() {
console.log('A show') ;
}
function B() {};
B.prototype.__proto__ = A.prototype; // 继承
let b = {
__proto__: B.prototype // 实现
}
b.show();
console.log(A.prototype.isPrototypeOf(B.prototype))
console.log(A.prototype === B.prototype.__proto__)
console.log(A.prototype.isPrototypeOf(b))
// class 呢?
class Common {
show() {
console.log('common show')
}
};
class User extends Common {}
let u = new User() ;
u.show() ;
console.log(Common.prototype.isPrototypeOf(User.prototype))
console.log(Common.prototype === User.prototype.__proto__)
console.log(Common.prototype.isPrototypeOf(u))
## 继承内置类(内置类功能增强)
继承 Array ,并作出增强
添加 first 、 max 方法
class Arr extends Array{
constructor(...args) {
super()
args.forEach(item => this.push(item));
}
first() {
return this[0];
}
max() {
return this.sort((a,b) => b-a)[0] ;
}
}
let hd = new Arr(22,1,3,4,5,102);
console.log(hd)
console.log(hd.first())
console.log(hd.max())
# mixin:“多继承”/接口模式
// mixin
// 工具类
let Tool = {
max(key) {
return this.data
.sort((l,r) => r[key]-l[key])[0] ;
}
}
let Arr = {
count(key) {
return this.data.reduce((previous, current) => {
return previous+=current[key];
}, 0);
}
}
// 实体类
class Lesson {
constructor(lessons) {
this.lessons = lessons ;
}
get data() {
return this.lessons;
}
};
// mixin!!
Object.assign(Lesson.prototype, Tool, Arr)
// 数据
const data = [
{name:'js', price: 100, click: 1},
{name:'mysql', price: 212, click: 2} ,
{name:'vue.js', price: 90, click: 3}
];
// 测试
let hd = new Lesson(data) ;
console.log('max price:',hd.max('price')) ;
console.log('total click:', hd.count('click'))
文章目录
# 综合案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style> div { width: 400px; height: 100px; } div>dt { color: aliceblue; background-color: black; } div>dd { height: inherit; background-color: yellowgreen; } </style>
</head>
<body>
<div class="slide s1">
<dt>点击</dt>
<dd>
<div>内容1</div>
</dd>
<dt>点击</dt>
<dd>
<div>内容2</div>
</dd>
<dt>点击</dt>
<dd>
<div>内容2</div>
</dd>
</div>
<script> class Animation { constructor(el) { this.el = el ; this.timeout = 1 ; this.speed = 1 ; this.isShow = true ; this.defaultHeight = this.height ; } get height(){ return window.getComputedStyle(this.el).height.slice(0,-2)*1 ; // *1 转换为数值类型 } set height(height) { this.el.style.height = height+ 'px' ; } hide(callback) { this.isShow = false ; let id = setInterval(() => { if(this.height<=0) { clearInterval(id) callback && callback() ; return ; } this.height = this.height - this.speed ; }, this.timeout); } show(callback) { this.isShow = true ; let id = setInterval(() => { if(this.height>=this.defaultHeight) { clearInterval(id) ; callback && callback() return ; } this.height = this.height + this.speed ; }, this.timeout); } } class Slide { constructor(el) { this.el = document.querySelector(el) ; this.links = this.el.querySelectorAll('dt'); // 链接 this.panels = [...this.el.querySelectorAll('dd')] .map(item => new Panel(item) ); // 面板 this.bind() ; } bind() { this.links.forEach((element, index) => { element.addEventListener('click', () => { this.action(index) ; }) }); } action(index) { Panel.hideAll(Panel.filter(this.panels, index), ()=> { this.panels[index].show(); }) ; } } class Panel extends Animation{ constructor(el) { super(el) ; } static num = 0 ; static hideAll(items, callback) { if(Panel.num>0) return ; items.forEach(item => { Panel.num++ ; item.hide(() => { Panel.num--; }) }) callback && callback() ; } static filter(items , index) { return items.filter((item, i) => i!=index ) } } let s = new Slide('.s1'); </script>
</body>
</html>