《JavaScript 闯关记》之原型及原型链

原型链是一种机制,指的是 JavaScript 每个对象都有一个内置的 __proto__ 属性指向创建它的构造函数的 prototype(原型)属性。原型链的作用是为了实现对象的继承,要理解原型链,需要先从函数对象constructornewprototype__proto__ 这五个概念入手。

函数对象

前面讲过,在 JavaScript 里,函数即对象,程序可以随意操控它们。比如,可以把函数赋值给变量,或者作为参数传递给其他函数,也可以给它们设置属性,甚至调用它们的方法。下面示例代码对「普通对象」和「函数对象」进行了区分。

普通对象:

1
2
var o1 = {};
var o2 = new Object();

函数对象:

1
2
3
function f1(){};
var f2 = function(){};
var f3 = new Function('str','console.log(str)');

简单的说,凡是使用 function 关键字或 Function 构造函数创建的对象都是函数对象。而且,只有函数对象才拥有 prototype (原型)属性。

constructor 构造函数

函数还有一种用法,就是把它作为构造函数使用。像 ObjectArray 这样的原生构造函数,在运行时会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而自定义对象类型的属性和方法。如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
};
}
var person1 = new Person("Stone", 28, "Software Engineer");
var person2 = new Person("Sophie", 29, "English Teacher");

在这个例子中,我们创建了一个自定义构造函数 Person(),并通过该构造函数创建了两个普通对象 person1person2,这两个普通对象均包含3个属性和1个方法。

你应该注意到函数名 Person 使用的是大写字母 P。按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。这个做法借鉴自其他面向对象语言,主要是为了区别于 JavaScript 中的其他函数;因为构造函数本身也是函数,只不过可以用来创建对象而已。

阅读更多

《JavaScript 闯关记》之作用域和闭包

作用域和闭包是 JavaScript 最重要的概念之一,想要进一步学习 JavaScript,就必须理解 JavaScript 作用域和闭包的工作原理。

作用域

任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在 JavaScript 中,变量的作用域有全局作用域和局部作用域两种。

全局作用域(Global Scope)

在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下三种情形拥有全局作用域:

1.最外层函数和在最外层函数外面定义的变量拥有全局作用域,例如:

1
2
3
4
5
6
7
8
var global = "global"; // 显式声明一个全局变量
function checkscope() {
var local = "local"; // 显式声明一个局部变量
return global; // 返回全局变量的值
}
console.log(global); // "global"
console.log(checkscope()); // "global"
console.log(local); // error: local is not defined.

上面代码中,global 是全局变量,不管是在 checkscope() 函数内部还是外部,都能访问到全局变量 global

2.所有末定义直接赋值的变量自动声明为拥有全局作用域,例如:

1
2
3
4
5
6
function checkscope() {
var local = "local"; // 显式声明一个局部变量
global = "global"; // 隐式声明一个全局变量(不好的写法)
}
console.log(global); // "global"
console.log(local); // error: local is not defined.

上面代码中,变量 global 未用 var 关键字定义就直接赋值,所以隐式的创建了全局变量 global,但这种写法容易造成误解,应尽量避免这种写法。

3.所有 window 对象的属性拥有全局作用域

一般情况下,window 对象的内置属性都拥有全局作用域,例如 window.namewindow.locationwindow.top 等等。

阅读更多

如何排版 微信公众号「代码块」之 MarkEditor

前段时间写过一篇文章 如何排版微信公众号「代码块」,讲的是如何使用浏览器插件 Markdown Here 来排版代码块。虽然用 Markdown Here 排版出来的样式还不错,但存在一个问题,就是代码之间的换行会全部丢失,需要手动进行调整。如果文章中代码较多的话,调整起来还是挺费劲的。

而我近期写的文章,常常会罗列大量代码,导致每次在公众号发文,都要花1个多小时来调整样式,真是难受想哭。

双11期间,看到 池建强老师 公众号 MacTalk 的文章 如何优雅的购买 Mac 软件。不出我意料,他果然推荐了一家专卖正版 Mac 软件的淘宝店铺 数码荔枝。秉承「老池推荐必精品」的原则,我如愿的淘到了微信公众号排版神器 MarkEditor。当时,该软件有着「双11特惠」和「老池特惠」双重加持,我仅用了 76.5 就拿下了官网售价 128 的 Pro 版本(得意的笑)。

阅读更多

《JavaScript 闯关记》之事件

JavaScript 程序采用了异步事件驱动编程模型。在这种程序设计风格下,当文档、浏览器、元素或与之相关的对象发生某些有趣的事情时,Web 浏览器就会产生事件(event)。例如,当 Web 浏览器加载完文档、用户把鼠标指针移到超链接上或敲击键盘时,Web 浏览器都会产生事件。如果 JavaScript 应用程序关注特定类型的事件,那么它可以注册当这类事件发生时要调用的一个或多个函数。请注意,这种风格并不只应用于 Web 编程,所有使用图形用户界面的应用程序都采用了它,它们静待某些事情发生(即,它们等待事件发生),然后它们响应。

请注意,事件本身并不是一个需要定义的技术名词。简而言之,事件就是 Web 浏览器通知应用程序发生了什么事情,这种在传统软件工程中被称为观察员模式。

事件流

当浏览器发展到第四代时(IE4 及 Netscape Communicator 4),浏览器开发团队遇到了一个很有意思的问题:页面的哪一部分会拥有某个特定的事件?要明白这个问题问的是什么,可以想象画在一张纸上的一组同心圆。如果你把手指放在圆心上,那么你的手指指向的不是一个圆,而是纸上的所有圆。两家公司的浏览器开发团队在看待浏览器事件方面还是一致的。如果你单击了某个按钮,他们都认为单击事件不仅仅发生在按钮上。换句话说,在单击按钮的同时,你也单击了按钮的容器元素,甚至也单击了整个页面。

事件流描述的是从页面中接收事件的顺序。但有意思的是,IE 和 Netscape 开发团队居然提出了差不多是完全相反的事件流的概念。IE 的事件流是事件冒泡流,而 Netscape Communicator 的事件流是事件捕获流。

事件冒泡

IE 的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。以下面的HTML页面为例:

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<head>
<title>Event Bubbling Example</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>

如果你单击了页面中的 <div> 元素,那么这个 click 事件会按照如下顺序传播:

  1. <div>
  2. <body>
  3. <html>
  4. document

也就是说,click 事件首先在 <div> 元素上发生,而这个元素就是我们单击的元素。然后,click 事件沿 DOM 树向上传播,在每一级节点上都会发生,直至传播到 document 对象。下图展示了事件冒泡的过程。

阅读更多

《JavaScript 闯关记》之 DOM(下)

Element 类型

除了 Document 类型之外,Element 类型就要算是 Web 编程中最常用的类型了。Element 类型用于表现 XML 或 HTML 元素,提供了对元素标签名、子节点及特性的访问。Element 节点具有以下特征:

  • nodeType 的值为1;
  • nodeName 的值为元素的标签名;
  • nodeValue 的值为 null
  • parentNode 可能是 DocumentElement
  • 其子节点可能是 ElementTextCommentProcessingInstructionCDATASectionEntityReference

要访问元素的标签名,可以使用 nodeName 属性,也可以使用 tagName 属性;这两个属性会返回相同的值(使用后者主要是为了清晰起见)。以下面的元素为例:

1
<div id="myDiv"></div>

阅读更多