面向对象的Javascript如何用于DOM操作



我了解OOP的主要原理,并且我有点知道如何将其实现到JS中。

function Person(name) {
this.name = name;
this.speak = function(msg) {
console.log('Person says:' + msg);
}
}
var dad = new Person('David');
dad.speak('I am your dad!');

上面的脚本只不过是在控制台中打印出一条消息。我不明白我们如何用这种技术接近 DOM。也许是这样的?

function Person(target, name) {
this.target = target;
this.name = name;
this.speak = function(msg) {
this.target.find('.speech-bubble').html(msg);
}
}
var dad = new Person($('#dad'), 'David');
dad.speak('I am your dad!');

虽然这似乎不是一个好方法。

我们如何通过OO Javascript使用对象,方法,构造函数等操作DOM?

关于 OO,如果你打算采用面向 DOM 的代码,你不会太远。

我会说一个类应该代表DOM上的一个组件/元素。它的方法作为状态管理部分。但老实说,这里没有正确的答案。这只是设计面向 DOM 的部分的 OO 的一种方式。

例:

const basicClassName = 'component';
const basicTemplate = '<h1>This is my basic component</h1>';
class MyComponent {
constructor(template = basicTemplate, className = basicClassName) {
this.template = template;
this.className = className;

this.element = document.createElement('div');
this.element.className = className;
this.element.innerHTML = template;
this.element.onclick = this.onClick.bind(this);
this.element.style.cursor = 'pointer';
}

onClick() {
this.element.classList.toggle('clicked');
}
}
const component = new MyComponent();
const container = document.querySelector('.container');
container.appendChild(component.element);
body {
font-size: 14px;
}
.component {
display: block;
padding: 1.3em;
box-shadow: 1px 1px 4px lightgray;
}
.clicked {
background-color: papayawhip;
}
<div class="container"></div>

你需要了解的是Prototype的概念。

使用new创建实例时,您正在基于原型构造对象。

请考虑以下事项:

function Person(name) {
this.name = name;
this.speak = function (msg) {
console.log('Person says:' + msg);
};
}
var dad = new Person('David');
dad.speak('I am your dad!');
console.log('Is dad.speak equal to dad.speak?', dad.speak === dad.speak);
var mom = new Person('David');
console.log('Is mom.speak equal to dad.speak?', mom.speak === dad.speak);

每次你构造一个新的Person实例时,一个新的speak原型现在漂浮在你的逻辑中的某个地方。这是非常低效的。

为了解决这个问题,我们需要修改函数的prototype

function Person(name) {
this.name = name;
}
Person.prototype.speak = function (msg) {
console.log('Person says:' + msg);
};
var dad = new Person('David');
dad.speak('I am your dad!');
console.log('Is dad.speak equal to dad.speak?', dad.speak === dad.speak);
var mom = new Person('David');
console.log('Is mom.speak equal to dad.speak?', dad.speak === dad.speak);

这样,我们只在继承到所有实例的prototype上创建一次函数。这更容易维护,效率也更高。

现在我们可以通过他们的prototype扩展 DOM 对象,但不建议这样做,因为您开始弄乱 Web 标准,使故障排除变得更加困难。

Array.prototype.isLengthGreaterThanFive = function(thisArg) {
return this.length > 5;
};
console.log([1, 2, 3, 4].isLengthGreaterThanFive(), [1, 2, 3, 4, 5, 6].isLengthGreaterThanFive());

处理此问题的更好方法是创建扩展对象或仅使用函数:

//Using functions
function isLengthGreaterThanFive(array) {
return array.length > 5;
}
console.log(isLengthGreaterThanFive([1, 2, 3, 4]), isLengthGreaterThanFive([1, 2, 3, 4, 5, 6]));

//Using a wrapper class
var MyArray = (function() {
function MyArray(array) {
if (array === void 0) {
array = [];
}
this.array = array;
}
MyArray.prototype.isLengthGreaterThanFive = function() {
return this.array.length > 5;
};
return MyArray;
}());
console.log(new MyArray([1, 2, 3, 4]).isLengthGreaterThanFive(), new MyArray([1, 2, 3, 4, 5, 6]).isLengthGreaterThanFive());

使用类的好处是我们可以扩展我们对对象的想法:

//Base class
function Person(firstname, lastname, says) {
if (firstname === void 0) {
firstname = "Leonado";
}
this.firstname = firstname;
if (lastname === void 0) {
lastname = "Da Vinci";
}
this.lastname = lastname;
if (says === void 0) {
says = "hello";
}
this.says = says;
}
//Base methods
Person.prototype.iAm = function () {
return this.firstname + " " + this.lastname;
};
Person.prototype.Speak = function () {
return this.says + " my name is " + this.iAm();
};
//Extended class
function Warrior(firstname, lastname, says) {
//Call in constructor
Person.call(this, firstname, lastname, says);
}
//Inheriting
Warrior.prototype = Object.create(Person.prototype);
Warrior.prototype.constructor = Warrior;
//Overruling "Speak"
Warrior.prototype.Speak = function () {
return "My name is " + this.iAm() + ", " + this.says;
};
console.log([new Warrior("Peter", "Allan", "Ahoyhoy").Speak(), new Person("Peter", "Allan", "Ahoyhoy").Speak()]);

在上面的例子中,我们扩展了Person的原型Warrior以便我们保留Person的功能,然后简单地修改Warrior的不同之处。通过这种方式,我们可以重用原型方法iAm,我们可以专注于只更改Speak方法中需要更改的内容。

编辑 1

我注意到问题发生了一些变化,为时已晚。

你可以像对待 JavaScript 中的任何其他类一样对待 DOM 元素。以下设置具有共享单个DIV的所有PersonsspeakUp

var Person = (function () {
function Person(age, firstname, lastname) {
if (age === void 0) { age = 50; }
if (firstname === void 0) { firstname = "Peter"; }
if (lastname === void 0) { lastname = "Venkman"; }
this.age = age;
this.firstname = firstname;
this.lastname = lastname;
}
Person.prototype.speakUp = function () {
Person.bubble.innerHTML = this.firstname + " " + this.lastname + " is " + this.age + " years old";
};
return Person;
}());
Person.bubble = document.createElement("div");
document.body.appendChild(Person.bubble);
setInterval(function () {
var p = new Person(Math.floor(Math.random() * 100));
p.speakUp();
}, 3000);

这很容易成为每PersonDIV,或者所有Person共享的引用DOM对象(document.getElementById)。

编辑 2

回应您的评论:

在JavaScript中,一切都是本质上和object。您创建一个函数,它使用函数名称注册一个object,并返回和instanceobject。像ArraysStringsDOM元素和自定义函数之类的东西都隐藏在幕后的一些主object。每次创建新的ArrayDOM元素或任何内容时,它都会引用其主对象(称为原型)。这称为原型链。

如果你看我上面的第二个例子,当dad.speak被称为JavaScript时,首先在实例中搜索一个speak属性,但它不会找到一个,因为我们没有像在示例一中那样分配它,因为它是特定于实例的。

然后 JavaScript 将尝试在prototype链上一级,在这里它将找到一个匹配的属性并使用它。通过这种方式,我们可以更改 JavaScript 中自定义现有元素的默认行为。

这个想法是,如果你有一些原型的所有实例都应该具有的属性,那么我们只需修改一次原型,它们都会inherit这个属性。

这样想吧。如果你要用JavaScript描述地球上的所有生物,你会想要某种形式的分组。例如,第一级类似于Exists对象,它带有名称和 id 的属性。从这里您可以创建PlantAnimal,并让它们都继承Exists的原型。现在我们可以创建一个继承PlantFlower类和一个继承FlowerRose类,依此类推。

这个想法是通过遗传以一种对人类有意义的方式应用你的属性(猫头鹰会飞,因为它是鸟/鲨鱼会游泳,因为它是鱼)。将它们绑定在有意义的级别,以逻辑模式继承并有效地利用您的时间。

如果您仍然感到困惑,请尝试查找prototype教程。

这里有一个很好的Youtube视频来解释它:

https://www.youtube.com/watch?v=PMfcsYzj-9M

最新更新