我了解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
的所有Persons
speakUp
:
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);
这很容易成为每Person
的DIV
,或者所有Person
共享的引用DOM对象(document.getElementById)。
编辑 2
回应您的评论:
在JavaScript中,一切都是本质上和object
。您创建一个函数,它使用函数名称注册一个object
,并返回和instance
该object
。像Arrays
、Strings
、DOM
元素和自定义函数之类的东西都隐藏在幕后的一些主object
。每次创建新的Array
或DOM
元素或任何内容时,它都会引用其主对象(称为原型)。这称为原型链。
如果你看我上面的第二个例子,当dad.speak
被称为JavaScript时,首先在实例中搜索一个speak
属性,但它不会找到一个,因为我们没有像在示例一中那样分配它,因为它是特定于实例的。
然后 JavaScript 将尝试在prototype
链上一级,在这里它将找到一个匹配的属性并使用它。通过这种方式,我们可以更改 JavaScript 中自定义或现有元素的默认行为。
这个想法是,如果你有一些原型的所有实例都应该具有的属性,那么我们只需修改一次原型,它们都会inherit
这个属性。
这样想吧。如果你要用JavaScript描述地球上的所有生物,你会想要某种形式的分组。例如,第一级类似于Exists
对象,它带有名称和 id 的属性。从这里您可以创建Plant
和Animal
,并让它们都继承Exists
的原型。现在我们可以创建一个继承Plant
的Flower
类和一个继承Flower
Rose
类,依此类推。
这个想法是通过遗传以一种对人类有意义的方式应用你的属性(猫头鹰会飞,因为它是鸟/鲨鱼会游泳,因为它是鱼)。将它们绑定在有意义的级别,以逻辑模式继承并有效地利用您的时间。
如果您仍然感到困惑,请尝试查找prototype
教程。
这里有一个很好的Youtube视频来解释它:
https://www.youtube.com/watch?v=PMfcsYzj-9M