JS为什么要用面向对象的写法,下面引用一些理论性的知识来介绍一下面向对象。
面向对象的几大特点:
1封装好处:
复用,降低冗余度,更有利于模块化编程,能够为变量函数提供更多的保护。使用对象来封装变量和函数。
2继承代码中继承:
子类获得父类的属性和方法(成员)的一种方式。
js的继承实现方法:
1属性拷贝(浅拷贝)
2属性拷贝(深拷贝)
3原型式继承
4原型链继承
5借用构造函数
6组合继承
3多态多种形态特点:
对于同一个操作(指令),不同的对象表现出不同的反应 隐藏不同好处:灵活
面向对象具有4个主要特性:
唯一 :
每个对象都有自身唯一的标识,通过这种标识,可找到相应的对象。在对象的整个生命期中,它的标识都不改变。
抽象:
将具有一致的属性和行为的对象抽象成类。反映了与应用有关的重要性质,而忽略其他一些无关内容。
继承:
继承子类自动共享父类数据结构和方法的机制,这是类之间的一种关系,继承性是面向对象思想最重要的一点。
多态:
多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。
面向对象与面向过程到底有什么区别?
可维护性强
写代码简单,维护起来却不容易。
经过深思熟虑的抽象,松散的代码可以被重构为“有胳膊有腿”的个体,
属于它的变量被定义成属性,相关的函数片段被定义成它的行为(方法),看到这样清晰的结构,对这些函数片段和散落的变量就会一目了然其作用。
可复用性强
如果做一个轮播图,可能100行之内就可以搞定。但有很多页面都需要你这个轮播图,但是有些许不同,比如轮播时间间隔,轮播方向,切换图片后的行为(轮播动画结束时需要执行的链接地址更新,统计等等等回调函数)。
或者,一个页面有两处轮播,而且两处的class,行为不同,你会将那段代码改吧改吧复制成两个吗?
如果是面向对象,就可以将方法,属性抽象,初始化不同的实例,实例间求同存异,各自都有自己的状态,互不影响。
看了上面的介绍不知你是否对面向对象有了更深一步的了解,纯粹的看文字都是很枯燥乏味的。下面我们就通过实例来简单介绍一下面向对象的应用。
Hantao.gif
(简单的结构图,请不要计较页面的美观)
如图所示,在一个页面中我同时要用到两个或多个tab切换,如果分开写会造成大量的代码,增加dom的操作,同样会影响性能,所以这个时候我们就可以用面向对象的方法,来避免和减少此类缺点。
面向对象的写法只要分为三部分就可以将其看明白
1.将普通方法中的 公共变量 作为对象中的 this 下面的属性
2.将具体的功能函数 以 对象.protype.方法 的形式展示
3.修改方法中this的指向
function Tab(container, className, data) {
this.container = document.querySelector(container); // 获取到最大的盒子
this.titles = this.container.querySelector('.tab-title'); // 通过最大的盒子获取标题盒子
this.contents = this.container.querySelector('.contnet'); // 通过最大的盒子获取内容盒子
this.cur = className; // 将接收到的颜色类,命名为一个新的变量
this.data = data;
this.init(); // 在自身的方法上,让它自动执行init()函数
}
Tab.prototype = {
constructor: Tab, // 手动的将constructor,指向了Tab构造函数
init: function() {
this.render(); // 把渲染页面和事件都绑在了原型上,可是原型不会自动调用,需要绑定一个事件来自动执行原型上的方法
this.addEvent()
},
addEvent: function() { // 绑定事件 // 事件放进对象中,所以要将其改成(key:值)对的形式
var that = this; // 为了避免和事件内的this发生冲突,所以提前将外面的this指向重新命名
that.titles.addEventListener('click', function (e) { // 此时的titles是指调用它的,所以要添加this
var event = e || window.e;
var tar = event.target || event.srcElement;
if (tar.nodeName === 'LI') {
var idx = tar.dataset.id;
// console.log(tar.dataset.id) // 获取下标的方法,提前在渲染页面时通过data-设置下标,用目标元素
// console.log(tar.getAttribute('data-id')) // 通过getAttribute获取设置的data-id的下标
for (var i = 0; i < this.children.length; i++) {
this.children[i].className = '';
that.contents.children[i].className = '';
}
tar.className = that.cur;
that.contents.children[idx].className = that.cur;
}
})
},
render: function() { // 渲染页面
var data = this.data; // 这个函数里面的data都是外面函数里面的获取到的,所以将this.data命名了一个新的变量,方便里面调用不用更改
var tHtml = '';
var cHtml = '';
for (var i = 0; i < data.length; i++) {
tHtml += `<li data-id="${i}">${data[i].title}</li>`;
cHtml += `<div >${data[i].content.text}</div>`;
}
this.titles.innerHTML = tHtml; // 这里面的标题和内容都是当前,所以前面都要加this
this.contents.innerHTML = cHtml;
this.titles.children[0].className = this.cur;
this.contents.children[0].className = this.cur;
}
}
new Tab('#container', 'active', data); // 创建构造函数传参(第1.页面内容最大盒子,第2.新添加颜色的类,第3.渲染页面的数据)
new Tab('#container2', 'active', data2); // 封装好方法后,如果需要重复多个,直接新建构造函数、重复盒子框架、渲染页面内容就可以了
以上代码并没有考虑兼容优化等问题,纯粹只是介绍面向对象的方法,如有强迫症的大神也可以将其进行优化,很希望看到大神提点。
一个太少,那就再来一个,轮播图算的上是应用极为广泛的了。我们常用一些框架来完成实现,但很多面试官会出题让面试者用原生JS来写轮播图,有的甚至会要求用面向对象的方式,那么就来个面向对象的原生JS无缝轮播图希望能对要面试的有所帮助。
Hantao.giffunction Boon(containet, option) {
this.container = document.querySelector(containet); // 获取到最大的盒子,通过大盒子再去获取盒子内的内容,这样如果页面有多个轮播图或内容时,不会导致混乱
this.boonWidth = this.container.clientWidth; // 大盒子的宽度=容器可视区域的宽度
this.wrap = this.container.querySelector('.wrap'); // 图片的盒子
this.lBtn = this.container.querySelector('.lBtn');
this.rBtn = this.container.querySelector('.rBtn'); // 获取左右按钮
this.moving = false; // 在图片移动未结束时,避免事件重复,增加一个判断,初始为false
this.moveTime = (option && option.moveTime) || 0.8; // 接收判断是否有其他对象传入,如有接收moveTime值(图片运行时间),如果没有设置成默认的值
this.autoPlays = (option && option.autoPlays) || false; // 设置是否自动播放,设置的值为true,否则为false
this.autoTime = (option && option.autoTime) || 1000; // 设置自动播放的时间
this.pageContainer = option && option.pagei ? this.container.querySelector(option.pagei) : null; // 获取页码的容器
this.init(); // 将原型上所有要执行的函数都封装进一个函数内,统一在构造函数中进行调用,版面整齐
}
Boon.prototype = {
constructor: Boon, // 手动的将原型指向构造函数
init() {
this.initWrap(); // 初始化轮播图
this.pageContainer && this.pages(); // 初始化页码
this.pageContainer && this.addPage(); // 初始化页码点击跳转事件
this.addEventBtn(); // 添加左右按钮事件
this.addEventWrap(); // 监听图片盒子添加移动事件
this.autoPlays && this.autoPlay(); // autoPlays为true时执行autoPlay函数
this.containers();
},
// 初始化轮播图
initWrap() {
var startImg = this.wrap.children[0].cloneNode(true); // 克隆第一个和最后一个子节点
var endImg = this.wrap.children[this.wrap.children.length - 1].cloneNode(true);
this.wrap.appendChild(startImg); // 在最后把第一张插入
this.wrap.insertBefore(endImg, this.wrap.children[0]); // 在第一张前面插入最后一张
// 重置图片盒子的宽度
for(var i = 0; i < this.wrap.children.length; i++) { // 大盒子的宽度要由内容撑起
this.wrap.children[i].style.width = this.boonWidth + 'px';
}
this.wrap.style.width = this.wrap.children.length * this.boonWidth + 'px'; // 设置大盒子的宽度
this.wrap.style.transform = `translate3d(-${this.boonWidth}px, 0, 0)`; // 初始向左移动一个图片的位置,因为在前面插入了一张图片,移动一个图片的位置,刚好显示的是正确的第一张图片
this.index = 1; // 设置初始下标从1开始
},
// 添加左右按钮事件
addEventBtn() {
var self = this;
this.rBtn.addEventListener('click', function() {
if(self.moving) return; // 如果初始值为true时,说明有图片正在移动,所以直接返回,不要执行下面的代码
self.index++;
self.move();
})
this.lBtn.addEventListener('click', function() {
if(self.moving) return;
self.index--;
self.move();
})
},
// 移动的事件,每次在点击按钮后进行调用
move() {
this.moving = true; // 在图片移动时将初始值设置为true
this.wrap.style.transition = `transform ${this.moveTime}s`;
this.wrap.style.transform = `translate3d(-${this.boonWidth * this.index}px, 0, 0)`;
this.pageContainer && this.changPage(); // 让图片在移动时,执行页码的函数进行关联
},
// 监听图片盒子添加移动事件
addEventWrap() {
var self = this; // 将实例的this指向存为一个变量,方便在事件函数内使用,方便区别this的指向
this.wrap.addEventListener('transitionend', function() { // transitionend:监听动画结束的事件
if(self.index === self.wrap.children.length - 1) { // 判断右边按钮的边界值
self.index = 1; // 将index的值设为1(真正第一张图片的下标)
self.wrap.style.transition = 'none'; // 移动图片的执行的时间设为0
self.wrap.style.transform = `translate3d(-${self.boonWidth * self.index}px, 0, 0)`; // 移动到指定的位置
}
if(self.index <= 0) { // 判断左边按钮的边界值
self.index = self.wrap.children.length - 2; // 下标设为图片的总数-2,(为倒数第2张图片的位置)
self.wrap.style.transition = 'none'; // 移动图片的执行的时间设为0
self.wrap.style.transform = `translate3d(-${self.boonWidth * self.index}px, 0, 0)`; // 移动到指定的位置
}
self.moving = false; // 当图片移动结束之后,将初始值设为false
})
},
// 添加自动轮播事件
autoPlay() {
var self = this;
this.time = setInterval(function() {
self.rBtn.click();
}, self.autoTime); // 执行定时器时,判断设置的间隔时间为多久
},
// 给container添加滑入滑出事件
containers() {
var self = this;
this.container.addEventListener('mouseenter', function() {
clearInterval(self.time);
self.time = null;
});
this.container.addEventListener('mouseleave', function() {
self.autoPlay();
})
},
// 渲染页码
pages() {
var len = this.wrap.children.length -2;
var html = '';
for(var i = 0; i < len; i++) {
html += `<span data-id="${i + 1}"></span>`;
}
this.pageContainer.innerHTML = html; // 渲染添加页码
this.pageContainer.children[0].classList.add('color'); // 给初始页码添加一个颜色类
},
// 添加页码关联事件
changPage() {
for(var i = 0; i < this.pageContainer.children.length; i++) {
this.pageContainer.children[i].classList.remove('color');
} // 初始化轮播图时,设置了index的初始值
var index = (this.index <= 0 ? index = this.pageContainer.children.length - 1 : this.index - 1) % this.pageContainer.children.length; // 因为实际图片的起始下标要比页码下标多1位,所以将实例中的index的下标-1,最后一个页码结束后得到一个下标为5的数,如果不取余,没有第5个页码,所以无法调回第1个,所以5%4得1,所以页码会回到第一个页码上
// 设置新的下标= (如果图片的下标<=0,新下标=图片子元素的长度-1,否则就图片下标-1) 括号中得到的数 % 页码所有子元素的长度 后得到的返回值赋给新的index
this.pageContainer.children[index].classList.add('color');
},
// 添加页码点击跳转事件(事件委托)
addPage() {
var self = this; // 区分实例的this指向和事件内部的this指向
this.pageContainer.addEventListener('click', function(e) { // 事件委托,2级事件
var event = e || window.event; // 兼容取到evenet(事件对象)
var tar = event.target || event.srcElement; // 兼容取到事件目标的节点
if(tar.nodeName === 'SPAN') { // 判断目标是否为span标签
var index = tar.dataset.id; // 渲染页面时给每一个标签元素都设置一个下标
self.index = index; // 把实例的下标与页码的下标同步
self.move(); // 同时调用移动事件
}
});
}
}
new Boon('#sw', { // 实例化传入大盒子
// moveTime: 0.1, // 将图片运行的时间设置成可更改的
autoPlays: true, // 是否开启自动播放
autoTime: 2000, // 如果设置间隔时间就按间隔计算,否则按照默认运行时间
pagei: '#page', // 页码的大盒子
});
对于面向对象更多的还是要在工作中去运用,希望今天的内容对需要想了解的朋友有所帮助,由于案例简陋,难免会有问题,所以希望大家多多指点~!
网友评论