浅析JavaScript的面向对象编程
一.面向对象的简单案列(Tab栏切换)
<main>
<div class="tabsbox" id="tab">
<!-- tab 标签 -->
<nav class="fisrstnav">
<ul>
<li class="liactive"><span>测试1</span><span class="iconfont icon-guanbi"></span></li>
<li><span>测试2</span><span class="iconfont icon-guanbi"></span></li>
<li><span>测试3</span><span class="iconfont icon-guanbi"></span></li>
</ul>
<div class="tabadd">
<span>+</span>
</div>
</nav>
<!-- tab 内容 -->
<div class="tabscon">
<section class="conactive">测试1</section>
<section>测试2</section>
<section>测试3</section>
</div>
</div>
</main>
* {
margin: 0;
padding: 0;
}
ul li {
list-style: none;
}
main {
width: 960px;
height: 500px;
border-radius: 10px;
margin: 50px auto;
}
main h4 {
height: 100px;
line-height: 100px;
text-align: center;
}
.tabsbox {
width: 900px;
margin: 0 auto;
height: 400px;
border: 1px solid lightsalmon;
position: relative;
}
nav ul {
overflow: hidden;
}
nav ul li {
float: left;
width: 100px;
height: 50px;
line-height: 50px;
text-align: center;
border-right: 1px solid #ccc;
position: relative;
}
.liactive {
border-bottom: 2px solid green;
z-index: 9;
}
#tab input {
width: 80%;
height: 60%;
}
nav ul li span:last-child {
position: absolute;
user-select: none;
font-size: 12px;
top: -18px;
right: 0;
display: inline-block;
height: 20px;
}
.tabadd {
position: absolute;
/* width: 100px; */
top: 0;
right: 0;
}
.tabadd span {
display: block;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
border: 1px solid #ccc;
float: right;
margin: 10px;
user-select: none;
}
.tabscon {
width: 100%;
height: 300px;
position: absolute;
padding: 30px;
top: 50px;
left: 0px;
box-sizing: border-box;
border-top: 1px solid #ccc;
}
.tabscon section,
.tabscon section.conactive {
display: none;
width: 100%;
height: 100%;
}
.tabscon section.conactive {
display: block;
}
var that;
class Tab{
constructor(id){
that = this
// 这里是不需要更改的节点
this.main = document.querySelector(id)
this.add = this.main.querySelector('.tabadd') // 添加按钮
this.fsection = this.main.querySelector('.tabscon')
this.ul = this.main.querySelector('ul')
this.init()
}
updateNode(){
// 这里在添加或者删除的时候需要更新的
this.lis = this.main.querySelectorAll('li')
this.sections = this.main.querySelectorAll('section')
this.remove = this.main.querySelectorAll('.icon-guanbi')
this.spans = this.main.querySelectorAll('.fisrstnav li span:first-child')
}
init(){
this.updateNode()
this.add.onclick = this.addTab;
for(var i=0;i<this.lis.length;i++){
this.lis[i].index = i
this.lis[i].onclick = this.toggleTab;
this.remove[i].onclick = this.removeTab;
this.spans[i].ondblclick = this.editTab;
this.sections[i].ondblclick = this.editTab;
}
}
addTab(){
that.clearClass()
var random = Math.random();
var li = '<li class="liactive"><span>新选项卡</span><span class="iconfont icon-guanbi"></span></li>';
var section = '<section class="conactive">测试 ' + random + '</section>';
that.ul.insertAdjacentHTML('beforeend', li);
// 'beforebegin':元素自身的前面。
// 'afterbegin':插入元素内部的第一个子节点之前。
// 'beforeend':插入元素内部的最后一个子节点之后。
// 'afterend':元素自身的后面。
that.fsection.insertAdjacentHTML('beforeend', section);
that.init();
}
clearClass(){
for(var i=0;i<this.lis.length;i++){
this.lis[i].className = ""
this.sections[i].className = ""
}
}
toggleTab(){
// 这里的this指向已经改变成了每个li
that.clearClass()
this.className = 'liactive'
that.sections[this.index].className = 'conactive'
}
removeTab(e){
e.stopPropagation(); // 阻止冒泡 因为父级也有点击事件
var index = this.parentNode.index // 看看对应li的索引是多少
that.lis[index].remove()
that.sections[index].remove()
that.init()
if(document.querySelector('liactive')) return // 如果删除的不是有激活的项
if(index===0){
that.lis[index].click()
return
}
index--
that.lis[index] && that.lis[index].click() // 手动调用toggleTab
}
editTab(){
var str = this.innerHTML
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
this.innerHTML = '<input type="text"/>' // 在span或section中插入input输入框
var input = this.children[0]
input.value = str // 将原来的值赋值给input输入框
input.select()
input.onblur = function(){
// 注意这里的this指向又变为了input输入框
this.parentNode.innerHTML = this.value
}
input.onkeyup = function(e){
if(e.keyCode === 13){
// 回车键
this.blur() // 手动调用onblur事件
}
}
}
}
new Tab('#tab')
二.JS是面向对象编程的语言吗?
首先JS是一个基于对象的语言不是真正面向对象的编程语言,在ES6之前没有类的概念,但类是面向对象的核心。虽然ES6引入了类的概念,但是只实现了类的继承,而封装与多态并没有实现。
- ES6之前是通过构造函数和原型实现面向对象编程
- ES6通过类实现面向对象编程
三.类
class Star{
constructor(uname,age){
this.uname = uname
this.age = age
}
}
// 利用类创建对象 new
var p = new Star('zcl',22)
四.构造函数
function Star(uname,age){
this.uname = uname
this.age = age
this.sing = function(){
console.log('我会唱歌')
}
}
var p = new Star('zcl',20)
五.new命令的内部原理
- 创建一个空对象,作为将要返回的对象实例
- 将这个空的对象的原型对象,指向了构造函数的prototype属性对象
- 将这个实例对象的值赋值给函数内部的this
- 执行构造函数内部的代码
六.原型链之__proto__与prototype
6.1 对应名称
- 原型prototype
- 原型链__proto__
6.2 从属关系
- prototype:是一个函数的属性 是一个对象__proto__:是对象的一个属性 是一个对象
- 对象的__proto__保存着该对象的构造函数的prototype
6.3 prototype与__proto__
function Test(){
this.a = 1
}
const test = new Test()
console.log(Test.prototype) // prototype是每个函数的属性
console.log(test.__proto__) // __proto__是每个对象的属性
console.log(Test.prototype === test.__proto__) // 对象的__proto__保存着该对象的构造函数的prototype
又因为Test.prototype也是一个对象所以它也有自己的__proto__
console.log(Test.prototype.__proto__ === Object.prototype) // true
那么Object.prototype也是一个对象也有其__proto__
console.log(Object.prototype.__proto__) // null
所以说原型链就是一个以对象为基准 以__proto__为连接的链条 一直到Object.prototype
console.log(Test.__proto__ === Function.prototype) // true 对象的__proto__等于构造其对象的函数的prototype
console.log(Function._proto_ === Function.prototype) //true 底层规定的 因为它构造了它本身
特殊的:函数既是Function又是Object
6.4 constructor
每个对象在创建时都会自动拥有一个构造函数属性constructor,指向构造函数的引用.
console.log(test.constructor) //Test
console.log(test.__proto__.constructor) //Test
constructor属性是继承自原型对象,所以test.constructor可以理解为是顺着__proto__来找到的。
七.继承
7.1 ES6的继承方式
class Father {
constructor(x,y){
this.x = x
this.y = y
}
sum(){
console.log(this.x+this.y)
}
say(){
console.log('Father')
}
}
class Son extends Father{
constructor(x,y,z){
super(x,y) // 调用父类的构造函数并将参数传递进去
this.x = x
this.y = y
this.z = z // 子类独有的属性
}
say(){
super.say() // 调用父类的普通函数
console.log('Son')
}
subtract(){ // 扩展子类的独有方法
console.log(this.x-this.y)
}
}
var son = new Son(1,2,3)
ES6是通过extends关键字实现继承
7.2 ES6之前的继承方式
function Father(uname,age){
this.uname = uname
this.age = age
}
// Son.prototype = Father.prototype // 这样做确实实现了子类继承了父类的方法 但是子类的方法也会显示到父类上去
// 剥离子类原型对象 和 父类原型对象的 指向
Son.prototype = new Father() // 这里会改变Son.prototype的指向 因为new Father里的constructor是Father 以及一些方法和属性
Son.prototype.constructor = Son // 如果利用了对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
function Son(uname,age){
Father.call(this,uname,age)
}
Father.prototype.money = function(){
console.log(100000)
}
Son.prototype.exam = function(){
console.log('exam')
}
var son = new Son('zcl',23)
ES6之前的继承方式主要是根据call方法实现继承,继承方式的优劣可以参考juejin.cn/post/688858… 这篇掘金博客
7.3 create是不是继承的一种方式
var a = {
name:1,
getX:function(){
console.log('X');
}
};
var b = Object.create(a); // 直接将a放到了b的原型链上 这是不是也是一种继承的方式
b.name = 'zcl'
console.log(b)
// name: "zcl"
// __proto__:
// getX: ƒ ()
// name: 1
// __proto__: Object
console.log(b.name) // zcl
create()中的参数作为返回实例对象b的原型对象,在a中定义的属性和方法,都能够被b实例对象继承下来。
八.类的本质
// ES6之前 构造函数+原型实现面向对象编程
//1.构造函数有原型对象prototype
//2.构造函数原型对象prototype 里面有constructor 指向构造函数本身
//3.构造函数可以通过原型对象添加方法
//4.构造函数创建的实例对象有__proto__ 指向 构造函数的原型对象
// ES6 通过类 实现面向对象编程
class Star{
}
console.log(typeof Star) //function 类的本质其实还是一个函数 我们可以理解为类就是构造函数的另一种写法
console.log(Star.prototype) // 类也有原型对象
console.log(Star.prototype.constructor)
Star.prototype.sing = function(){console.log(111)} // 类可以通过原型对象添加方法
// 所以ES6中的类就是语法糖
九.涉及的其他问题
9.1 静态成员
Star.sex = '男' // 静态成员 在构造函数本身上添加的成员 只能通过构造函数来访问
9.2 判断一个对象是否有该属性
hasOwnProperty()仅从对象本身去寻找是否有该属性
test.hasOwnProperty("a")
"a" in test // 是从整个原型链上去找