在JavaScript中,一切都是对象。
element=document.querySelector(selectors)
使用深度优先遍历,element是一个DOM元素,selectors是一个字符串,包含一个或者多个CSS选择器。如果没有找到匹配元素,则返回null,如果找到国歌匹配元素,则返回找到的第一个匹配到的元素。传递给 querySelector的字符串参数必须符合CSS语法。
document.getElementById(id);
document.getElementsByTag(classname);
通过id获取元素和通过class获取元素
Object.style.property;
Object.className;
Object.disabled;
改变元素样式、获取元素classname和设置元素disabled属性
4.js代码错误一般包括两种,语法错误和逻辑错误。
5.你可以先初始化变量然后再声明一个变量,这样与先声明变量再初始化是一样的。这叫做变量提升(var hoisting)。
6.变量命名习惯
An aside on variable naming rules
You can call a variable pretty much anything you like, but there are limitations. Generally you should stick to just using Latin characters (0-9, a-z, A-Z) and the underscore character.
You shouldn't use other characters because they may cause errors or be hard to understand by an international audience.
Don't use underscores at the start of variable names — this is used in certain JavaScript constructs to mean specific things, so may get confusing.
Don't use numbers at the start of variables. This isn't allowed and will cause an error.
A safe convention to stick to is so-called "lower camel case", where you stick together multiple words, using lower case for the whole first word and then capitalize subsequent words. We've been using this for our variable names in the article so far.
Make variable names intuitive, so they describe the data they contain. Don't just use single letters/numbers, or big long phrases.
Variables are case sensitive — so myage is a different variable to myAge.
One last point — you also need to avoid using JavaScript reserved words as your variable names — by this, we mean the words that make up the actual syntax of JavaScript! So you can't use words like var, function, let, and for as variable names. Browsers will recognize them as different code items, and so you'll get errors.
7.Variable types
There are a few different types of data we can store in variables (data types). In this section we'll describe these in brief, then in future articles you'll learn about them in more detail.
So far we've looked at the first two, but there are others.
Numbers
You can store numbers in variables, be those whole numbers like 30 (also called integers) or decimal numbers like 2.456 (also called floats or floating point numbers). JavaScript doesn't have different data types for different number types, like some programming languages do. When you give a variable a number value, you don't include quotes:
var myAge = 22;
Strings
Strings are pieces of text. When you give a variable a string value, you need to wrap it in single or double quote marks, otherwise JavaScript will try to intepret it as another variable name.
var dophinGoodbye="so long and thanks for all the fish";
Booleans
Booleans are true/false values — they can have two values, true or false. These are generally used to test a condition, after which code is run as appropriate. So for example, a simple case would be:
var iAmAlive= true;
Whereas in reality it would be used more like this:
var test= 6<3;
This is using the "less than" operator (<) to test whether 6 is less than 3. As you might expect, it will return false, because 6 is not less than 3! You will learn a lot more about such operators later on in the course.
Arrays
An array is a single object that contains multiple values enclosed in square brackets and separated by commas. Try entering the following lines into your console:
var myNameArray=['Chris','Bob','Jim];
var myNumberArray=[10,15,45];
Arrays
An array is a single object that contains multiple values enclosed in square brackets and separated by commas. Try entering the following lines into your console:
myNameArray[0];
myNumberArray[2];
The square brackets here contain an index value that specifies the position of the value you want returned. You might have noticed that computers count from 0, instead of 1 like us humans do.
You'll learn a lot more about arrays in a future article.
Objects
In programming, an object is a structure of code that models a real life object. You can have a simple object that represents a car park and contains information about its width and length, or you could have an object that represents a person, and contains data about their name, height, weight, what language they speak, how to say hello to them, and more.
Try entering the following line into you console:
var dog={name:'Spot',breed:'Dalmatian'};
To retrieve the information stored in the object, you can use the following syntax:
dog.name
We won't be looking at objects any more for now — you can learn more about those in a future module.
8.Loose typing
JavaScript is a "loosely typed language", which means that, unlike some other languages, you don't need to specify what data type a variable will contain (e.g. number? string?).
For example if you declare a variable and give it a value with quotes round it, the browser will know it is a string:
var myString='hello';
It will still be a string, even if it contains numbers, so be careful:
var myNumber='500';
typeof(myNumber);
myNumber=500;
typeof(myNumber);
Try entering the four lines above into your console one by one, and see what the results are (don't type in the comments). You'll notice that we are using a special function called typeof() — this returns the data type of the variable you pass to it. The first time it is called in the above sequence, it should return string, as at that point the myNumber variable contains a string, '500'. Have a look and see what it returns the second time you call it.
8.数字类型
整数: 就是整数,例如 10, 400, 或者 -5.
浮点数: (浮点) 有小数点或小数位,例如 12.5,和 56.7786543。
双精度: 双精度是一种特定类型的浮点数,它们具有比标准浮点数更高的精度(这意味着它们精确到更大的小数位数)。
9.Comparison operators
You may see some people using == and != in their code for equality and non-equality — these are valid operators in JavaScript, but they differ from ===/!== — the former test whether the values are the same, but the datatype can be different, whereas the latter strict versions test if the value and the dataype are the same. The strict versions tend to result in less errors going undetected, so we are recommending that you use those.
10.Escaping characters in a string
To fix our previous problem code line, we need to escape the problem quote mark. Escaping characters means that we do something to them to make sure they are recognized as text, not part of the code. In JavaScript, we do this by putting a backslash just before the character. Try this:
var bigMouth='I\'ve got no right to take my place...';
var myNum=Number(myString);
var string=myNum.toString();
利用以上方法来进行字符串和数字类型的转换
12.Useful string methods
//在浏览器开发者控制台输入以下内容
var browser='mozilla';
browser.length;
browser[0];
browser.indexOf('zilla');
browser.slice(0,3);
browser.slice(2);
//slice的第二个值是可选的,如果输入的值是负数,那么代表length+index。
var rad="I Love You";
rad.toUpperCase();
rad.toLowerCase();
browser.replace('moz','van');
13.Some useful array methods
var myData = 'Manchester,London,Liverpool,Birmingham,Leeds,Carlisle';
var myArray = myData.split(',');
myArray;
var myNewString = myArray.join(',');
myNewString;
myArray.push('Cardiff');
myArray;
myArray.push('Bradford', 'Brighton');
myArray;
var newLength = myArray.push('Bristol');
//array.push()会产生一个返回值,返回数组的长度
myArray;
newLength;
myArray.pop();
var removedItem = myArray.pop();
//array.pop返回删除的值
myArray;
removedItem;
myArray.unshift('Edinburgh');//返回数组长度
myArray;
var removedItem = myArray.shift();//返回删除的值
myArray;
removedItem;
14.conditional statement
Any value that is not false, undefined, null, 0, NaN, or an empty string ('') actually returns true when tested as a conditional statement.
switch(expression){
case choice1:
run this code
break;
case choice2:
run this code
break;
//include as many cases as you like
default:
actually,just run this code
15.Loop statement
iteration
16.Function
Functions versus methods
One thing we need to clear up before we move on — technically speaking, built in browser functions are not functions — they are methods.
The distinction is that methods are functions defined inside objects. Built-in browser functions (methods) and variables (which are called properties) are stored inside structured objects, to make the code more efficient and easier to handle.
Anonymous function
You can also assign an anonymous function to be the value of a variable, for example:
var myGreeting = function() {
alert('hello');
}
myGreeting();
Note: The same scoping rules do not apply to loop (e.g. for() { ... }) and conditional blocks (e.g. if() { ... }) — they look very similar, but they are not the same thing! Take care not to get these confused.
17.Return Value
利用isNaN()来测试是否是一个数字。
18.Introduction to Events
Note that event handlers are sometimes called event listeners — they are pretty much interchangeable for our purposes, although strictly speaking they work together. The listener listens out for the event happening, and the handler is the code that is run in response to it happening.
Note: It is important to note that web events are not part of the core JavaScript language — they are defined as part of the JavaScript APIs built into the browser.
onclick
onfocus
onblur
ondblcilck
onkeypress
onkeyup
onkeydown
onmouseover
onmouseout
window.onkeypress, window.onkeydown, window.onkeyup — The color will change when a key is pressed on the keyboard. keypress refers to a general press (button down and then up), while keydown and keyup refer to just the key down and key up parts of the keystroke, respectively. Note that it doesn't work if you try to register this event handler on the button itself — we've had to register it on the button itself — we've had to register it on the window object, which represents the entire browser window.
<button onclick="bgChange()">Press me</button>
You'll find HTML attribute equivalents for many of the event handler properties; however, you shouldn't use these — they are considered bad practice. It might seem easy to use an event handler attribute if you are just doing something really quick, but they very quickly become unmanageable and inefficient.
The newest type of event mechanism is defined in the Document Object Model (DOM) Level 2 Events Specification, which provides browsers with a new function — addEventListener(). This functions in a similar way to the event handler properties, but the syntax is obviously different. We could rewrite our random color example to look like this:
var btn = document.querySelector('button');
function bgChange() {
var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
document.body.style.backgroundColor = rndCol;
}
btn.addEventListener('click', bgChange);
Note that it is perfectly appropriate to put all the code inside the addEventListener() function, in an anonymous function, like this:
btn.addEventListener('click', function() {
var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
document.body.style.backgroundColor = rndCol;
});
This mechanism has some advantages over the older mechanisms discussed earlier. For a start, there is a counterpart function, removeEventListener()
, which removes a previously added listener. For example, this would remove the listener set in the first code block in this section:
btn.removeEventListener('click', bgChange);
Second, you can also register multiple handlers for the same listener. The following two handlers would not be applied:
myElement.onclick = functionA;
myElement.onclick = functionB;
As the second line would overwrite the first value of onclick set. This would work, however:
myElement.addEventListener('click', functionA);
myElement.addEventListener('click', functionB);
Both functions would now run when the element is clicked.
What mechanism should I use?
Of the three mechanisms, you definitely shouldn't use the HTML event handler attributes — these are outdated, and bad practice, as mentioned above.
The other two are relatively interchangeable, at least for simple uses:
Event handler properties have less power and options, but better cross browser compatibility (being supported as far back as Internet Explorer 8). You should probably start with these as you are learning.
DOM Level 2 Events (addEventListener(), etc.) are more powerful, but can also become more complex and are less well supported (supported as far back as Internet Explorer 9). You should also experiment with these, and aim to use them where possible.
The main advantages of the third mechanism are that you can remove event handler code if needed, using removeEventListener(), and you can add multiple listeners of the same type to elements if required.
Event objects
Sometimes inside an event handler function you might see a parameter specified with a name such as event, evt, or simply e. This is called the event object, and it is automatically passed to event handlers to provide extra features and information. For example, let's rewrite our random color example again slightly:
function bgChange(e) {
var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
e.target.style.backgroundColor = rndCol;
console.log(e);
}
btn.addEventListener('click', bgChange);
Here you can see that we are including an event object, e, in the function, and in the function setting a background color style on e.target — which is the button itself. The target property of the event object is always a reference to the element that the event has just occurred upon. So in this example we are setting a random background color on the button, not the page.
Preventing default behaviour
The trouble comes when the user has not submitted the data correctly — as a developer, you'll want to stop the submission to the server and give them an error message telling them what's wrong and what needs to be done to put things right. Some browsers support automatic form data validation features, but since many don't, you are advised to not rely on those, and implement your own validation checks. Let's look at a simple example.
<form>
<div>
<label for="fname">First name: </label>
<input id="fname" type="text">
</div>
<div>
<label for="lname">Last name: </label>
<input id="lname" type="text">
</div>
<div>
<input id="submit" type="submit">
</div>
</form>
<p></p>
var form = document.querySelector('form');
var fname = document.getElementById('fname');
var lname = document.getElementById('lname');
var submit = document.getElementById('submit');
var para = document.querySelector('p');
form.onsubmit = function(e) {
if (fname.value === '' || lname.value === '') {
e.preventDefault();
para.textContent = 'You need to fill in both names!';
}
}
Event bubbling and capture
The final subject to cover here is something that you'll not come across often, but it can be a real pain if you don't understand it. Event bubbling and capture are two mechanisms that describe what happens when two handlers of the same event type are activated on one element. Let's look at an example to make this easier — open up the show-video-box.html example in a new tab (and the source code in another tab.) It is also available live below:
This is a pretty simple example that shows and hides a <div> with a <video> element inside it:
<button>Display video</button>
<div class="hidden">
<video>
<source src="rabbit320.mp4" type="video/mp4">
<source src="rabbit320.webm" type="video/webm">
<p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p>
</video>
</div>
When the <button> is clicked, the video is displayed, by changing the class attribute on the <div> from hidden to showing (the example's CSS contains these two classes, which position the box off the screen and on the screen, respectively):
btn.onclick = function() {
videoBox.setAttribute('class', 'showing');
}
We then add a couple more onclick event handlers — the first one to the <div> and the second one to the <video>. The idea is that when the area of the <div> outside the video is clicked, the box should be hidden again; when the video itself is clicked, the video should start to play.
videoBox.onclick = function() {
videoBox.setAttribute('class', 'hidden');
};
video.onclick = function() {
video.play();
};
But there's a problem — currently when you click the video it starts to play, but it causes the <div> to also be hidden at the same time. This is because the video is inside the <div> — it is part of it — so clicking on the video actually runs both the above event handlers.
Bubbling and capturing explained
When an event is fired on an element that has parent elements (e.g. the <vedio> in our case), modern browsers run two different phases — the capturing phase and the bubbling phase. In the capturing phase:
The browser checks to see if the element's outer-most ancestor (<html>) has an onclick event handler registered on it in the capturing phase, and runs it if so.
Then it moves on to the next element inside <html> and does the same thing, then the next one, and so on until it reaches the element that was actually clicked on.
In the bubbling phase, the exact opposite occurs:
The browser checks to see if the element that was actually clicked on has an onclick event handler registered on it in the bubbling phase, and runs it if so.
Then it moves on to the next immediate ancestor element and does the same thing, then the next one, and so on until it reaches the <html> element.
In modern browsers, by default, all event handlers are registered in the bubbling phase. So in our current example, when you click the video, the click event bubbles from the <video> element outwards to the <html> element. Along the way:
It finds the video.onclick... handler and runs it, so the video first starts playing.
It then finds the videoBox.onclick... handler and runs it, so the video is hidden as well.
Fixing the problem with stopPropagation()
This is annoying behaviour, but there is a way to fix it! The standard event object has a function available on it called stopPropagation(),which when invoked on a handler's event object makes it so that handler is run, but the event doesn't bubble any further up the chain, so no more handlers will be run.
We can therefore fix our current problem by changing the second handler function in the previous code block to this:
video.onclick = function(e) {
e.stopPropagation();
video.play();
};
Event delegation
Event delegation
Bubbling also allows us to take advantage of event delegation — this concept relies on the fact that if you want some code to run when you click on any one of a large number of child elements, you can set the event listener on their parent and have the effect of the event listener bubble to each child, rather than having to set the event listener on every child individually.
A good example is a series of list items — if you want each one of them to pop up a message when clicked, you can can set the click event listener on the parent ul, and it will bubble to the list items.
19.对象
一个对象是一个包含相关资料和功能的集体(通常由一些变量和函数组成,我们称之为对象里面的属性和方法)。
对象的字面量(literal)——手动的写出对象的内容来创建一个对象。
对象的名字表现为一个命名空间(namespace),它必须写在第一位——当你想访问对象内部的属性或方法时,然后是一个点(.),紧接着是你想要访问的项目,标识可以是简单属性的名字(name),或者是数组属性的一个子元素,又或者是对象的方法调用。
person.age
person.interests[1]
person.bio()
可以用一个对象来做另一个对象成员的值。
外一种访问属性的方式是使用括号表示法(bracket notation),替代这样的代码
person.age
person.name.first
person['age']
person['name']['first']
这看起来很像访问一个数组的元素,从根本上来说是一回事儿,你使用了关联了值的名字,而不是索引去选择元素。难怪对象有时被称之为关联数组(associative array)了——对象做了字符串到值的映射,而数组做的是数字到值的映射。
比如说,我们想让用户能够在他们的数据里存储自己定义的值类型,通过两个input框来输入成员的名字和值,通过以下代码获取用户输入的值:
var myDataName = nameInput.value
var myDataValue = nameValue.value
我们可以这样把这个新的成员的名字和值加到person对象里:
person[myDataName] = myDataValue
为了测试这个功能,尝试在你的代码里添加以下几行,就在person对象的右花括号的下面:
var myDataName = 'height'
var myDataValue = '1.75m'
person[myDataName] = myDataValue
现在,保存并刷新,在输入框里输入以下代码:
person.height
点表示法只能接受字面量的成员的名字,不接受变量作为名字。
"this"的含义
greeting: function() {
alert('Hi! I\'m ' + this.name.first + '.');
}
你也许想知道"this"是什么,关键字"this"指向了当前代码运行时的对象( 原文:the current object the code is being written inside )——这里即指person对象。
Specialist classes
In this case we don't want generic people — we want teachers and students, which are both more specific types of people. In OOP, we can create new classes based on other classes — these new child classes can be made to inherit the data and code features of their parent class, so you can reuse functionality common to all the object types rather than having to duplicate it. Where functionality differs between classes, you can define specialized features directly on them as needed.
This is really useful — teachers and students share many common features such as name, gender, and age, so it is convenient to only have to define those features once. You can also define the same feature separately in different classes, as each definition of that feature will be in a different namespace. For example, a student's greeting might be of the form "Yo, I'm [firstName]" (e.g Yo, I'm Sam), whereas a teacher might use something more formal, such as "Hello, my name is [Prefix] [lastName]" (e.g Hello, My name is Mr Griffiths).
Note: The fancy word for the ability of multiple object types to implement the same functionality is polymorphism. Just in case you were wondering.
Constructors and object instances
Some people argue that JavaScript is not a true object-oriented language — for example it doesn't have a class statement for creating classes like many OO languages. JavaScript instead uses special functions called constructor functions to define objects and their features. They are useful because you'll often come across situations in which you don't know how many objects you will be creating; constructors provide the means to create as many objects as you need in an effective way, attaching data and functions to them as required.
When a new object instance is created from a constructor function, the functionality is not all copied over to the new object like "classic" OO languages — instead the functionality is linked to via a reference chain called a prototype chain (see Object prototypes). So this is not true instantiation, strictly speaking — JavaScript uses a different mechanism to share functionality between objects.
Let's explore creating classes via constructors and creating object instances from them in JavaScript. First of all, we'd like you to make a new local copy of the oojs.html file we saw in our first Objects article.
A simple example
1.Let's start by looking at how you could define a person with a normal function. Add this function below the existing code:
function createNewPerson(name) {
var obj = {};
obj.name = name;
obj.greeting = function () {
alert('Hi! I\'m ' + this.name + '.');
}
return obj;
}
2.You can now create a new person by calling this function — try the following lines in your browser's JavaScript console:
var salva = createNewPerson('salva');
salva.name;
salva.greeting();
This works well enough, but it is a bit longwinded; if we know we want to create an object, why do we need to explicitly create a new empty object and return it? Fortunately JavaScript provides us with a handy shortcut, in the form of constructor functions — let's make one now!
3.Replace your previous function with the following:
function Person(name) {
this.name = name;
this.greeting = function() {
alert('Hi! I\'m ' + this.name + '.');
};
}
The constructor function is JavaScript's version of a class. You'll notice that it has all the features you'd expect in a function, although it doesn't return anything or explicitly create an object — it basically just defines properties and methods. You'll see the this keyword being used here as well — it is basically saying that whenever one of the these object instances is created, the object's name property will be equal to the name value passed to the constructor call, and the greeting() method will use the name value passed to the constructor call too.
Note: A constructor function name usually starts with a capital letter — this convention is used to make constructor functions easier to recognize in code.
So how do we call a constructor to create some objects?
1.Add the following lines below your previous code addition:
var person1 = new Person('Bob');
var person2 = new Person('Sarah');
In each case, the new keyword is used to tell the browser we want to create a new object instance, followed by the function name with its required parameters contained in parentheses, and the result is stored in a variable.
2.Save your code and reload it in the browser, and try entering the following lines into your text input:
person1.name
person1.greeting()
person2.name
person2.greeting()
Other ways to create object instances
The Object() constructor
var person1 = new Object({
name : 'Chris',
age : 38,
greeting : function() {
alert('Hi! I\'m ' + this.name + '.');
}
});
Using the create() method
JavaScript has a built-in method called create(), which allows you to create a new object instance based on an existing object.
var person2 = Object.create(person1);
20.原型
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。
准确地说,这些属性和方法定义在 Object 的构造器函数之上,而非对象实例本身。
在传统的 OOP 中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制——而是在对象实例和它的构造器之间建立一个连接(作为原型链中的一节),以后通过上溯原型链,在构造器中找到这些属性和方法。
prototype 属性:继承成员被定义的地方
那么,那些继承的属性和方法在哪儿定义呢?如果你查看 Object 参考页,会发现左侧列出许多属性和方法——大大超过我们在 person1 对象中看到的继承成员的数量。某些属性或方法被继承了,而另一些没有——为什么呢?
原因在于,继承的属性和方法是定义在 prototype 属性之上的(你可以称之为子命名空间 (sub namespace) )——那些以 Object.prototype. 开头的属性,而非仅仅以 Object. 开头的属性。prototype 属性的值是一个对象,我们希望被原型链下游的对象继承的属性和方法,都被储存在其中。
于是 Object.prototype.watch()、Object.prototype.valueOf() 等等成员,适用于任何继承自 Object() 的对象类型,包括使用构造器创建的新的对象实例。Object.is()、Object.keys(),以及其他不在 prototype 对象内的成员,不会被“对象实例”或“继承自 Object() 的对象类型”所继承。这些方法/属性仅能被 Object() 构造器自身使用。
重要:prototype 属性大概是 JavaScript 中最容易混淆的名称之一。你可能会认为,这个属性指向当前对象的原型对象,其实不是(还记得么?原型对象是一个内部对象,应当使用 proto 访问)。prototype 属性包含(指向)一个对象,你在这个对象中定义需要被继承的成员。
create()
我们曾经讲过如何用 Object.create() 方法创建新的对象实例。例如,在上个例子的 JavaScript 控制台中输入:
var person2 = Object.create(person1);
create() 实际做的是从指定原型对象创建一个新的对象。这里以 person1 为原型对象创建了 person2 对象。在控制台输入:
person2.__proto__
结果返回 person1 对象。
constructor 属性
每个对象实例都具有 constructor 属性,它指向创建该实例的构造器函数。
例如,在控制台中尝试下面的指令:
person1.constructor
person2.constructor
都将返回 Person() 构造器,因为该构造器包含这些实例的原始定义。
一个小技巧是,你可以在 constructor 属性的末尾添加一对圆括号(括号中包含所需的参数),从而用这个构造器创建另一个对象实例。毕竟构造器是一个函数,故可以通过圆括号调用;只需在前面添加 new 关键字,便能将此函数作为构造器使用。
在控制台中输入:
var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain clim]);
现在尝试访问新建对象的属性,例如:
person3.name.first
person3.age
person3.bio()
正常工作。通常你不会去用这种方法创建新的实例;但如果你刚好因为某些原因没有原始构造器的引用,那么这种方法就很有用了。
此外,constructor 属性还有其他用途。比如,想要获得某个对象实例的构造器的名字,可以这么用:
instanceName.constructor.name
具体地,像这样:
person1.constructor.name
我们的代码中定义了构造器,然后用这个构造器创建了一个对象实例,此后向构造器的 prototype 添加了一个新的方法:
function Person(first, last, age, gender, interests) {
// 属性与方法定义
};
var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']);
Person.prototype.farewell = function() {
alert(this.name.first + ' has left the building. Bye for now!');
}
但是 farewell() 方法仍然可用于 person1 对象实例——旧有对象实例的可用功能被自动更新了。这证明了先前描述的原型链模型。这种继承模型下,上游对象的方法不会复制到下游的对象实例中;下游对象本身虽然没有定义这些方法,但浏览器会通过上溯原型链、从上游对象中找到它们。这种继承模型提供了一个强大而可扩展的功能系统。
事实上,一种极其常见的对象定义模式是,在构造器(函数体)中定义属性、在 prototype 属性上定义方法。如此,构造器只包含属性定义,而方法则分装在不同的代码块,代码更具可读性。例如:
// 构造器及其属性定义
function Test(a,b,c,d) {
// 属性定义
};
// 定义第一个方法
Test.prototype.x = function () { ... }
// 定义第二个方法
Test.prototype.y = function () { ... }
// 等等……
21.继承
比如我们想要创建一个Teacher类,就像我们前面在面向对象概念解释时用的那个一样。这个类会继承Person的所有成员,同时也包括:
一个新的属性,subject——这个属性包含了教师教授的学科。
一个被更新的greeting()方法,这个方法打招呼听起来比一般的greeting()方法更正式一点——对于一个教授一些学生的老师来说。
定义 Teacher() 构造函数
我们要做的第一件事是创建一个Teacher()构造器——将下面的代码加入到现有代码之下:
function Teacher(first, last, age, gender, interests, subject) {
Person.call(this, first, last, age, gender, interests);
this.subject = subject;
}
这在很多方面看起来都和Person的构造器很像,但是这里有一些我们从没见过的奇怪玩意——call()函数。基本上,这个函数允许你调用一个在这个文件里别处定义的函数。第一个参数指明了在你运行这个函数时想对“this”指定的值,也就是说,你可以重新指定你调用的函数里所有“this”指向的对象。其他的变量指明了所有目标函数运行时接受的参数。
Note: In this case we specify the inherited properties when we create a new object instance, but note that you'll need to specify them as parameters in the constructor even if the instance doesn't require them to be specified as parameters (Maybe you've got a property that's set to a random value when the object is created, for example.)
As a note, we could have simply done this:
function Teacher(first, last, age, gender, interests, subject) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
this.subject = subject;
}
But this is just redefining the properties anew, not inheriting them from Person(), so it defeats the point of what we are trying to do. It also takes more lines of code.
Inheriting from a constructor with no parameters
Note that if the constructor you are inheriting from doesn't take its parameters from property values, you don't need to specify them as additional arguments in call(). So for example, if you had something really simple like this:
function Brick() {
width: 10,
height: 20
}
You could inherit the width and height properties by doing this (as well as the other steps described below, of course):
function BlueGlassBrick() {
Brick.call(this);
this.opacity: 0.5;
this.color: 'blue';
}
Note that we've only specified this inside call() — no other parameters are required as we are not inheriting any parameters, only properties. Of course, you probably wouldn't do something this simple in a constructor very often as it defeats the point of using them. But it does get the point across.
Setting Teacher()'s prototype and constructor reference
All is good so far, but we have a problem. We have defined a new constructor, and it has a prototype property that by default just contains a reference to the constructor function itself (try entering Teacher.prototype.constructor into your JavaScript console at this point). We need to get Teacher() to inherit the methods defined on Person()'s prototype. So how do we do that?
Add the following line below your previous addition:
Teacher.prototype = Object.create(Person.prototype);
Giving Teacher() a new greeting() function
To finish off our code, we need to define a new greeting() function on the Teacher() constructor.
The easiest way to do this is to define it on Teacher()'s prototype — add the following at the bottom of your code:
Teacher.prototype.greeting = function() {
var prefix;
if (this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
prefix = 'Mr.';
} else if (this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
prefix = 'Mrs.';
} else {
prefix = 'Mx.';
}
alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.');
};
This alerts the teacher's greeting, which also uses an appropriate name prefix for their gender, worked out using a conditional statement.
Object member summary
To summarize, you've basically got three types of property/method to worry about:
1.Those defined inside a constructor function that are given to object instances. These are fairly easy to spot — in your own custom code, they are the members defined inside a constructor using the this.x = x type lines; in built in browser code, they are the members only available to object instances (usually created by calling a constructor using the new keyword, e.g. var myInstance = new myConstructor()).
2.Those defined directly on the constructor themselves, that are available only on the constructor. These are commonly only available on built-in browser objects, and are recognized by being chained directly onto a constructor, not an instance. For example, Object.keys().
3.Those defined on a constructor's prototype, which are inherited by all instances and inheriting object classes. These include any member defined on a Constructor's prototype property, e.g. myConstructor.prototype.x().
22.Working with JSON data
JSON can exist as an object, or a string — the former is used when you want to read data out of the JSON, and the latter is used when you want to send the JSON across the network. This is not a big issue — JavaScript provides a global JSON object that has methods available for converting between the two.
A JSON object can be stored in its own file, which is basically just a text file with an extension of .json, and a MIME type of application/json.
JSON requires double quotes to be used to be valid. It is safest to write it with double quotes, not single quotes.
Converting between objects and text
The above example was simple in terms of accessing the JSON, because we set the XHR to return the response already in JSON format, using:
request.responseType = 'json';
But sometimes we aren't so lucky — sometimes we'll receive some JSON data formatted as a text string, and we'll want to convert it to an object. And when we want to send JSON data as some kind of message, we'll often need to convert it to a string for it to work correctly. Luckily, these two problems are so common in web development that a built-in JSON object was added to browsers quite a while ago, containing the following two methods:
parse(): Accepts a JSON object in text string form as a parameter, and returns the corresponding object.
stringify(): Accepts a JSON object as a parameter, and returns the equivalent text string form.
You can see the first one in action in our heroes-finished-json-parse.html example (see the source code) — this does exactly the same thing as the example we built up earlier, except that we set the XHR to return the JSON as text, then used parse() to convert it to an actual JSON object. The key snippet of code is here:
request.open('GET', requestURL);
request.responseType = 'text'; // now we're getting a string!
request.send();
request.onload = function() {
var superHeroesText = request.response; // get the string from the response
var superHeroes = JSON.parse(superHeroesText); // convert it to an object
populateHeader(superHeroes);
showHeroes(superHeroes);
}
As you might guess, stringify() works the opposite way. Try entering the following lines into your browser's JavaScript console on by one to see it in action:
var myJSON = { "name": "Chris", "age": "38" };
myJSON
var myString = JSON.stringify(myJSON);
myString
Here we're creating a JSON object, then checking what it contains, then converting it to a string using stringify() — saving the return value in a new variable — then checking it again.