javascript的特点
。。对于网页而言,Javascript无处不在,对于英语不好的人它简直是噩梦般的存在,但形式所逼,今天开始着手学习!希望自己能坚持下去。
从什么地方着手,我的目标是从大处着眼,从应用着眼,不抠细节,反正细节也记不住,到用到的时候在抠。
尽管编程是外行,也积累了一些常识,所以就从javascript的特点开始学习。
一、什么是Javascript?
JavaScript是一种基于对象(Object)和事件驱动(Event Driven)并具有相对安全性的客户端脚本语言。
这个定义非常好,几乎涵盖了Javascript的特点。
1、Javascript是脚本语言
脚本(Script)是一种纯文本保存的程序,脚本简单地说就是一条条的文字命令,脚本程序在执行时,需要一个解释器,将其一条条的翻译成机器可识别的指令,并按程序顺序执行。Javascript脚本的解释器叫Javascript解释器,它包含在浏览器中,由于浏览器的种类和版本不同,解释出来的东西也有区别,为了让大部分浏览器都能解释我们编写的脚步,目前我们不得不针对不同浏览器编写不同的脚本,所以很多时候Javascript是啰里啰嗦的。Javascript是顺序的解释性语言,它不像C,JAVA那样需要先编译。
跨平台:JavaScript依赖于浏览器本身,JavaScript跨平台,是因为主流浏览器有JavaScript解释器。
2、基于对象
基于对象,这个说法挺有趣,现在还有人在讨论Javascript是不是面向对象的语言,因为它的语法中没有class(类)。我们就把它理解成一种特殊的面向对象编程(OOP)语言吧,在 JavaScript 中,对象无处不在,几乎所有的东西都是对象,字符串、数值、数组、函数,日期,Boolean,RegExp(正则表达式),Null也是一个对象。对象是拥有属性和方法的数据。内建对象可以直接使用其属性和方法,是不是很方便呢?
3、事件驱动
JavaScript与HTML之间的交互是通过事件实现的,当我们与浏览器中 Web 页面进行某些类型的交互时(如点击按钮,鼠标滑过),事件就发生了。事件是发生在DOM上的,是DOM的重要组成部分,通过使用 JavaScript ,可以监听特定事件(DOM事件)的发生,并规定这些事件做出什么响应。
4、相对安全
JavaScript不被允许访问本地的硬盘,且不能将数据存入服务器,不允许对网络文档进行修改和删除,只能通过浏览器实现信息浏览或动态交互。从而有效地防止数据的丢失或对系统的非法访问。
二.JavaScript能做什么
JavaScript 被数百万计的网页用来改变样式、验证表单、检测浏览器、创建cookies,制作网页特效(如漂浮、滚动、滑动门等等),万年历,在线编辑器,js游戏, 同服务器进行数据交互(AJAX)......
三、JavaScript 三个组成部分
核心(ECMAScript)
文档对象模型(DOM)
浏览器对象模型(BOM)
ECMAScript 描述了以下内容:
- 语法
- 类型
- 语句
- 关键字
- 保留字
- 运算符
- 对象
ECMAScript 仅仅是一个描述,定义了脚本语言的所有属性、方法和对象
DOM(文档对象模型)是 HTML 和 XML 的应用程序接口(API)。DOM 将把整个页面规划成由节点层级构成的文档。HTML 或 XML 页面的每个部分都是一个节点的衍生物。DOM 通过创建树来表示文档,从而使开发者对文档的内容和结构具有空前的控制力。用 DOM API 可以轻松地删除、添加和替换节点。
浏览器对象模型(BOM)
BOM 主要处理浏览器窗口和框架,不过通常浏览器特定的 JavaScript 扩展都被看做 BOM 的一部分。这些扩展包括:
- 弹出新的浏览器窗口
- 移动、关闭浏览器窗口以及调整窗口大小
- 提供 Web 浏览器详细信息的定位对象
- 提供用户屏幕分辨率详细信息的屏幕对象
- 对 cookie 的支持
- IE 扩展了 BOM,加入了 ActiveXObject 类,可以通过 JavaScript 实例化 ActiveX 对象
由于没有相关的 BOM 标准,每种浏览器都有自己的 BOM 实现。
DOM(文档对象模型)简介
DOM(文档对象模型)针对HTML和XML文档的一个API. DOM可以将任何HTML或XML文档描绘成由多层节点构成的树形结构,它是中立于平台和语言的接口,允许程序和脚本动态地访问和更新文档的内容、结构和样式。
Javascript通常都是用来进行DOM操作和交互的。当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model)。
以下讨论的是HTML DOM。
每一个网页元素(一个HTML标签)被看做一个对象。文档对象模型通常被理解成一棵树的形状。树根是document对象,相当于最外层的标签的外围,也就是整个文档。树根之下(这棵树的图通常是倒着画,就好像遗传谱系或者家谱那样。树根就是唯一的共同祖先)是子一级的对象,子对象也有它自己的子对象,除了根对象以外,所有的对象都有自己的父对象,同一对象的子对象之间就是兄弟的关系。文档对象模型把整张网页组织成这样的一个树状的结构,树结构中的每一个元素都被视为一个节点(node)。包括JavaScript在内的各种编程语言都可以通过文档对象模型来访问和改变网页的各种细节。我们用JavaScript对网页进行的所有操作都是通过DOM进行的。DOM属于浏览器,而不是JavaScript语言规范里的规定的核心内容,所以如果你下载一个JavaScript语言的参考帮助文档来查的话,就连妇孺皆知的document.write方法也找不到。
DOM的所有节点类型都继承自Node类型,每个节点都拥有包含着关于节点某些信息的属性。这些属性是:nodeName(节点名称) nodeValue(节点值) nodeType(节点类型)。有12种DOM node类型,一般只讨论Element, Text, Document。
- Element节点在页面里展示的是一个元素,所以如果你有段落元素(
),你可以通过这个DOM节点来访问。
- Text节点在页面里展示的所有文本相关的元素,所以如果你的段落有文本在里面的话,你可以直接通过DOM的Text节点来访问这个文本
- Document节点代表是整个文档,它是DOM的根节点。
二、DOM对象的属性和方法:
一些常用的 HTML DOM 方法:
- getElementById(id) - 获取带有指定 id 的节点(元素)
- appendChild(node) - 插入新的子节点(元素)
- removeChild(node) - 删除子节点(元素)
一些常用的 HTML DOM 属性:
- innerHTML - 节点(元素)的文本值
- parentNode - 节点(元素)的父节点
- childNodes - 节点(元素)的子节点
- attributes - 节点(元素)的属性节点
- className - 设置或返回元素的 class 属性
一些 DOM 对象方法
下面列出一些常用的 DOM 对象方法:[1]
方法 | 描述 |
---|---|
getElementById() | 返回带有指定 ID 的元素。 |
getElementsByTagName() | 返回包含带有指定标签名称的所有元素的节点列表(集合/节点数组)。 |
getElementsByClassName() | 返回包含带有指定类名的所有元素的节点列表。 |
appendChild() | 把新的子节点添加到指定节点。 |
removeChild() | 删除子节点。 |
replaceChild() | 替换子节点。 |
insertBefore() | 在指定的子节点前面插入新的子节点。 |
createAttribute() | 创建属性节点。 |
createElement() | 创建元素节点。 |
createTextNode() | 创建文本节点。 |
getAttribute() | 返回指定的属性值。 |
setAttribute() | 把指定属性设置或修改为指定的值。 |
三、修改 HTML 元素
修改 HTML DOM 意味着许多不同的方面:
1、改变 HTML 内容
改变元素内容的最简答的方法是使用 innerHTML 属性。例: document.getElementById("p1").innerHTML="New text!";
2、 改变 CSS 样式
通过 HTML DOM,您能够访问 HTML 元素的样式对象。例:document.getElementById("p2").style.color="blue";
3、改变 HTML 属性
4、创建新的 HTML 元素
如需向 HTML DOM 添加新元素,您首先必须创建该元素(元素节点),然后把它追加到已有的元素上。
<!DOCTYPE html>
<html>
<body>
<div id="div1">
<p id="p1">This is a paragraph.</p>
<p id="p2">This is another paragraph.</p>
</div>
<script>
var para=document.createElement("p");
var node=document.createTextNode("This is new.");
para.appendChild(node);
var element=document.getElementById("div1");
element.appendChild(para);
</script>
</body>
</html>
View Code
<!DOCTYPE html>
<html>
<body>
<div id="div1">
<p id="p1">This is a paragraph.</p>
<p id="p2">This is another paragraph.</p>
</div>
<script>
var para=document.createElement("p");
var node=document.createTextNode("This is new.");
para.appendChild(node);
var element=document.getElementById("div1");
element.appendChild(para);
</script>
</body>
</html>
View Code
5、删除已有的 HTML 元素
6、改变事件(处理程序)
四、Event事件
浏览器事件是所有web程序的核心,通过这些事件我们定义将要发生的行为,DOM和JavaScript语言是2个单独的东西,浏览器事件是DOM API的一部分,而不是JavaScript的一部分, JavaScript用来指定事件处理程序。
事件类型
1、鼠标事件
‘mousedown’ – 鼠标设备按下一个元素的时候触发mousedown事件。
‘mouseup’ – 鼠标设备从按下的元素上弹起的时候触发mouseup事件。
‘click’ – 鼠标点击元素的时候触发click事件。
‘dblclick’ – 鼠标双击元素的时候触发dblclick事件。
‘mouseover’ – 鼠标移动到某元素上的时候触发mouseover事件。
‘mouseout’ – 鼠标从某元素离开的时候触发mouseout事件。
‘mousemove’ – 鼠标在某元素上移动但未离开的时候触发mousemove事件。
2、键盘事件
‘keypress’ – 按键按下的时候触发该事件。
‘keydown’ – 按键按下的时候触发该事件,并且在keypress事件之前。
‘keyup’ – 按键松开的时候触发该事件,在keydown和keypress事件之后。
3、表单事件
‘select’ – 文本字段(input, textarea等)的文本被选择的时候触发该事件。
‘change’ – 控件失去input焦点的时候触发该事件(或者值被改变的时候)。
‘submit’ – 表单提交的时候触发该事件。
‘reset’ – 表单重置的时候触发该事件。
‘focus’ – 元素获得焦点的时候触发该事件,通常来自鼠标设备或Tab导航。
‘blur’ – 元素失去焦点的时候触发该事件,通常来自鼠标设备或Tab导航。
4、其它事件
‘load’ – 页面加载完毕(包括内容、图片、frame、object)的时候触发该事件。
‘resize’ – 页面大小改变的时候触发该事件(例如浏览器缩放)。
‘scroll’ – 页面滚动的时候触发该事件。
‘unload’ – 从页面或frame删除所有内容的时候触发该事件(例如离开一个页面)。
事件处理程序(事件监听器)
如果在JavaScript 中分配事件处理函数, 需要首先获得要处理的对象的一引用,然后将函数赋值给对应的事件处理函数属性,请看一个简单的例子:
var link=document.getElementById("mylink");
link.onclick=function(){
alert("I was clicked !");
};
Event对象
属性:
bubbles 返回布尔值,指示事件是否是起泡事件类型。
cancelable 返回布尔值,指示事件是否可拥可取消的默认动作。
currentTarget 返回其事件监听器触发该事件的元素。
eventPhase 返回事件传播的当前阶段。
target 返回触发此事件的元素(事件的目标节点)。
timeStamp 返回事件生成的日期和时间。
type 返回当前 Event 对象表示的事件的名称。
方法:
initEvent() 初始化新创建的 Event 对象的属性。
preventDefault() 通知浏览器不要执行与事件关联的默认动作。
stopPropagation() 不再派发事件。
DOM事件流
DOM(文档对象模型)结构是一个树型结构,当一个HTML元素产生一个事件时,该事件会在元素结点与根节点之间按特定的顺序传播,路径所经过的节点都会收到该事件,这个传播过程可称为DOM事件流。事件顺序有两种类型:事件捕捉和事件冒泡。冒泡型事件从DOM树型结构上理解,就是事件由叶子节点沿祖先结点一直向上传递直到根节点。捕获型事件与冒泡型刚好相反,由DOM树最顶层元素一直到最精确的元素。
一、六种数据类型
原始类型(基本类型):按值访问,可以操作保存在变量中实际的值。包括:Number,Boolean,String,Undefined,Null,Symbol(es6新定义的)
引用类型:引用类型的值是保存在内存中的对象Object (注: Array是特殊的Object)。
二、判断数据类型
typeof方法 返回的类型
string:字符串类型
number:数字类型,包括整形,浮点型,NaN
function:function
object:数组,对象,null
boolean:true,false
undefined:undefined
PS:undefined是访问一个未初始化的变量时返回的值,而null是访问一个尚未存在的对象时所返回的值。因此,可以把undefined看作是空的变量,而null看作是空的对象。
二、显示与隐式数据转换
一)、利用js提供的函数parseInt() , parseFloat() , Number() , Boolean() , String()进行数据转换
1.parseInt()
1.1) 忽略字符串前面的空格,直至找到第一个非空字符,还会将数字后面的非数字的字符
串去掉。
1.2) 如果第一个字符不是数字符号或者符号,返回NaN
1.3) 会将小数取整。(向下取整)
2.Number()
2.1) 如果转换的内容本身就是一个数值类型的字符串,那么将来在转换的时候会返回自己。
2.2) 如果要转换的内容是空的字符串,空数组,null,那以转换的结果是0.
2.3) 如果是其它的字符,那么将来在转换的时候结果是NaN.
3.Boolean()
除了"",0,false,null,undefined,NaN返回false,其他全部返回true(包括空对象,空数组)
4.String()
String()和toString()方法都是将其它类型的变量转换为字符串的方法。但两者存在一定的区别:
x.toString(): 无法转换null和undefined:
二)、隐式类型转换指的是字符串和数值类型之间的转换,在进行字符串和数字之间进行减乘除取模运算或者进行比较运算时,他会自动把字符串转换为数字。转换数字的默认方法是调用Number(),进行加法运算则是将数字看成字符串进行拼接.
javascript中如何准确判断一个变量是什么,面试中这是考一个人基本功扎不扎实必定会问的一个问题。如果你还不是很清楚,相信这篇文章会对你有所帮助。
一,判断方法
1.typeof
我们能够使用typeof判断变量的身份,判断字符串得到string,数字和NaN得到number,函数会得到function等,但是判断数组,对象和null时都会得到object,详细请看js数据类型,这就是typeof的局限性,并不能准确的判断该变量的"真实身份"。那如何判断一个变量是数组还是对象?
2.instanceof
使用instanceof可以用来判断一个变量是数组还是对象,原理如下:
数组也是对象的一种,使用instanceof都会返回true
var arr = new Array();
var arr = ['aa','bb','cc'];
var obj = {
a: 'aa',
b: 'bb',
c: 'cc'
};
console.log(arr instanceof Array); //true
console.log(arr instanceof Object); //true
console.log(obj instanceof Array); //false
console.log(obj instanceof Object); //true
instanceof是如何判断的:
instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性,意思就是该变量通过原型链上能否找到构造函数的prototype 属性,还不清楚原型链的请看原型链,所以就能明白为什么instanceof判断一个变量可以分清楚它到底是数组还是对象:
Array.prototype === arr.__proto__
Object.prototype === arr.__proto__.__proto__
因为arr的原型链上存在Array.prototype和Object.prototype
只有Array类型的变量才会满足arr instanceof Array和arr instanceof Object都返回true,
也只有Object类型变量才满足obj instanceof Array返回false,obj instanceof Object返回true
3.constructor
var arr = ['aa','bb','cc'];
var obj = {
'a': 'aa',
'b': 'bb',
'c': 'cc'
};
console.log(arr.constructor === Array); //true
console.log(arr.constructor === Object); //false
console.log(obj.constructor === Object); //true
4.Object.prototype.toString.call()
Object.prototype.toString.call()方法可以精准判断变量类型,它返回[object constructorName]的字符串格式,这里的constructorName就是call参数的函数名
var a = NaN;
var b= '222';
var c = null;
var d = false;
var e = undefined;
var f = Symbol();
var arr = ['aa','bb','cc'];
var obj = {
'a': 'aa',
'b': 'bb',
'c': 'cc'
};
var res = Object.prototype.toString.call(arr);
console.log(res); //[object Array]
var res2 = Object.prototype.toString.call(obj);
console.log(res2); //[object Object]
var res3 = Object.prototype.toString.call(a);
console.log(res3); //[object Number]
var res4 = Object.prototype.toString.call(b);
console.log(res4); //[object String]
var res4 = Object.prototype.toString.call(c);
console.log(res4); //[object Null]
var res5 = Object.prototype.toString.call(d);
console.log(res5); //[object Boolean]
var res6 = Object.prototype.toString.call(e);
console.log(res6); //[object Undefined]
var res7 = Object.prototype.toString.call(f);
console.log(res7); //[object Symbol]
二,总结 判断简单数据类型可以用typeof,判断数组,对象使用instanceof,constructor和 Object.prototype.toString.call(),最好使用Object.prototype.toString.call(),更加精准
在这之前,我们需要来回顾一下执行上下文。
在前面几篇文章中,我有好几个地方都提到执行上下文的生命周期,为了防止大家没有记住,再次来回顾一下,如下图。
执行上下文生命周期
在执行上下文的创建阶段,会分别生成变量对象,建立作用域链,确定this指向。其中变量对象与作用域链我们都已经仔细总结过了,而这里的关键,就是确定this指向。
在这里,我们需要得出一个非常重要一定要牢记于心的结论,this的指向,是在函数被调用的时候确定的。也就是执行上下文被创建时确定的。因此我们可以很容易就能理解到,一个函数中的this指向,可以是非常灵活的。比如下面的例子中,同一个函数由于调用方式的不同,this指向了不一样的对象。
var a = 10;
var obj = {
a: 20
}
function fn () {
console.log(this.a);
}
fn(); // 10
fn.call(obj); // 20
每个函数都包含两个非继承而来的方法:call()方法和apply()方法。
call()和apply()方法时,就会改变this的指向。
除此之外,在函数执行过程中,this一旦被确定,就不可更改了。
var a = 10;
var obj = {
a: 20
}
function fn () {
this = obj; // 这句话试图修改this,运行后会报错
console.log(this.a);
}
fn();
一、全局对象中的this
关于全局对象的this,我之前在总结变量对象的时候提到过,它是一个比较特殊的存在。全局环境中的this,指向它本身。因此,这也相对简单,没有那么多复杂的情况需要考虑。
// 通过this绑定到全局对象
this.a2 = 20;
// 通过声明绑定到变量对象,但在全局环境中,变量对象就是它自身
var a1 = 10;
// 仅仅只有赋值操作,标识符会隐式绑定到全局对象
a3 = 30;
// 输出结果会全部符合预期
console.log(a1);
console.log(a2);
console.log(a3);
二、函数中的this
在总结函数中this指向之前,我想我们有必要通过一些奇怪的例子,来感受一下函数中this的捉摸不定。
// demo01
var a = 20;
function fn() {
console.log(this.a);
}
fn();
// demo02
var a = 20;
function fn() {
function foo() {
console.log(this.a);
}
foo();
}
fn();
// demo03
var a = 20;
var obj = {
a: 10,
c: this.a + 20,
fn: function () {
return this.a;
}
}
console.log(obj.c);
console.log(obj.fn());
这几个例子需要读者老爷们花点时间稍微感受一下,如果你暂时没想明白怎么回事,也不用着急,我们一点一点来分析。
分析之前,我们先直接了当抛出结论。
在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。
从结论中我们可以看出,想要准确确定this指向,找到函数的调用者以及区分他是否是独立调用就变得十分关键。
// 为了能够准确判断,我们在函数内部使用严格模式,因为非严格模式会自动指向全局
function fn() {
'use strict';
console.log(this);
}
fn(); // fn是调用者,独立调用
window.fn(); // fn是调用者,被window所拥有
在上面的简单例子中,fn()
作为独立调用者,按照定义的理解,它内部的this指向就为undefined。而window.fn()
则因为fn被window所拥有,内部的this就指向了window对象。
那么掌握了这个规则,现在回过头去看看上面的三个例子,通过添加/去除严格模式,那么你就会发现,原来this已经变得不那么虚无缥缈,已经有迹可循了。
但是我们需要特别注意的是demo03。在demo03中,对象obj中的c属性使用this.a + 20
来计算,而他的调用者obj.c
并非是一个函数。因此他不适用于上面的规则,我们要对这种方式单独下一个结论。
当obj在全局声明时,无论obj.c
在什么地方调用,这里的this都指向全局对象,而当obj在函数环境中声明时,这个this指向undefined,在非严格模式下,会自动转向全局对象。可运行下面的例子查看区别。
'use strict';
var a = 20;
function foo () {
var a = 1;
var obj = {
a: 10,
c: this.a + 20,
fn: function () {
return this.a;
}
}
return obj.c;
}
console.log(foo()); // 运行会报错
实际开发中,并不推荐这样使用this;
上面多次提到的严格模式,需要大家认真对待,因为在实际开发中,现在基本已经全部采用严格模式了,而最新的ES6,也是默认支持严格模式。
再来看一些容易理解错误的例子,加深一下对调用者与是否独立运行的理解。
var a = 20;
var foo = {
a: 10,
getA: function () {
return this.a;
}
}
console.log(foo.getA()); // 10
var test = foo.getA;
console.log(test()); // 20
foo.getA()
中,getA是调用者,他不是独立调用,被对象foo所拥有,因此它的this指向了foo。而test()
作为调用者,尽管他与foo.getA的引用相同,但是它是独立调用的,因此this指向undefined,在非严格模式,自动转向全局window。
稍微修改一下代码,大家自行理解。
var a = 20;
function getA() {
return this.a;
}
var foo = {
a: 10,
getA: getA
}
console.log(foo.getA()); // 10
灵机一动,再来一个。如下例子。
function foo() {
console.log(this.a)
}
function active(fn) {
fn(); // 真实调用者,为独立调用
}
var a = 20;
var obj = {
a: 10,
getA: foo
}
active(obj.getA);
三、使用call,apply显示指定this
JavaScript内部提供了一种机制,让我们可以自行手动设置this的指向。它们就是call与apply。所有的函数都具有这两个方法。它们除了参数略有不同,其功能完全一样。它们的第一个参数都为this将要指向的对象。
如下例子所示。fn并非属于对象obj的方法,但是通过call,我们将fn内部的this绑定为obj,因此就可以使用this.a访问obj的a属性了。这就是call/apply的用法。
function fn() {
console.log(this.a);
}
var obj = {
a: 20
}
fn.call(obj);
而call与applay后面的参数,都是向将要执行的函数传递参数。其中call以一个一个的形式传递,apply以数组的形式传递。这是他们唯一的不同。
function fn(num1, num2) {
console.log(this.a + num1 + num2);
}
var obj = {
a: 20
}
fn.call(obj, 100, 10); // 130
fn.apply(obj, [20, 10]); // 50
因为call/apply的存在,这让JavaScript变得十分灵活。因此就让call/apply拥有了很多有用处的场景。简单总结几点,也欢迎大家补充。
1、将类数组对象转换为数组
function exam(a, b, c, d, e) {
// 先看看函数的自带属性 arguments 什么是样子的
console.log(arguments);
// 使用call/apply将arguments转换为数组, 返回结果为数组,arguments自身不会改变
var arg = [].slice.call(arguments);
console.log(arg);
}
exam(2, 8, 9, 10, 3);
// result:
// { '0': 2, '1': 8, '2': 9, '3': 10, '4': 3 }
// [ 2, 8, 9, 10, 3 ]
//
// 也常常使用该方法将DOM中的nodelist转换为数组
// [].slice.call( document.getElementsByTagName('li') );
2、根据自己的需要灵活修改this指向
var foo = {
name: 'joker',
showName: function() {
console.log(this.name);
}
}
var bar = {
name: 'rose'
}
foo.showName.call(bar);
3、实现继承
// 定义父级的构造函数
var Person = function(name, age) {
this.name = name;
this.age = age;
this.gender = ['man', 'woman'];
}
// 定义子类的构造函数
var Student = function(name, age, high) {
// use call
Person.call(this, name, age);
this.high = high;
}
Student.prototype.message = function() {
console.log('name:'+this.name+', age:'+this.age+', high:'+this.high+', gender:'+this.gender[0]+';');
}
new Student('xiaom', 12, '150cm').message();
// result
// ----------
// name:xiaom, age:12, high:150cm, gender:man;
简单给有面向对象基础的朋友解释一下。在Student的构造函数中,借助call方法,将父级的构造函数执行了一次,相当于将Person中的代码,在Sudent中复制了一份,其中的this指向为从Student中new出来的实例对象。call方法保证了this的指向正确,因此就相当于实现了基层。Student的构造函数等同于下。
var Student = function(name, age, high) {
this.name = name;
this.age = age;
this.gender = ['man', 'woman'];
// Person.call(this, name, age); 这一句话,相当于上面三句话,因此实现了继承
this.high = high;
}
4、在向其他执行上下文的传递中,确保this的指向保持不变
如下面的例子中,我们期待的是getA被obj调用时,this指向obj,但是由于匿名函数的存在导致了this指向的丢失,在这个匿名函数中this指向了全局,因此我们需要想一些办法找回正确的this指向。
var obj = {
a: 20,
getA: function() {
setTimeout(function() {
console.log(this.a)
}, 1000)
}
}
obj.getA();
常规的解决办法很简单,就是使用一个变量,将this的引用保存起来。我们常常会用到这方法,但是我们也要借助上面讲到过的知识,来判断this是否在传递中被修改了,如果没有被修改,就没有必要这样使用了。
var obj = {
a: 20,
getA: function() {
var self = this;
setTimeout(function() {
console.log(self.a)
}, 1000)
}
}
另外就是借助闭包与apply方法,封装一个bind方法。
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments);
}
}
var obj = {
a: 20,
getA: function() {
setTimeout(bind(function() {
console.log(this.a)
}, this), 1000)
}
}
obj.getA();
当然,也可以使用ES5中已经自带的bind方法。它与我上面封装的bind方法是一样的效果。
var obj = {
a: 20,
getA: function() {
setTimeout(function() {
console.log(this.a)
}.bind(this), 1000)
}
}
四、构造函数与原型方法上的this
在封装对象的时候,我们几乎都会用到this,但是,只有少数人搞明白了在这个过程中的this指向,就算我们理解了原型,也不一定理解了this。所以这一部分,我认为将会为这篇文章最重要最核心的部分。理解了这里,将会对你学习JS面向对象产生巨大的帮助。
结合下面的例子,我在例子抛出几个问题大家思考一下。
function Person(name, age) {
// 这里的this指向了谁?
this.name = name;
this.age = age;
}
Person.prototype.getName = function() {
// 这里的this又指向了谁?
return this.name;
}
// 上面的2个this,是同一个吗,他们是否指向了原型对象?
var p1 = new Person('Nick', 20);
p1.getName();
我们已经知道,this,是在函数调用过程中确定,因此,搞明白new的过程中到底发生了什么就变得十分重要。
通过new操作符调用构造函数,会经历以下4个阶段。
- 创建一个新的对象;
- 将构造函数的this指向这个新对象;
- 指向构造函数的代码,为这个对象添加属性,方法等;
- 返回新对象。
因此,当new操作符调用构造函数时,this其实指向的是这个新创建的对象,最后又将新的对象返回出来,被实例对象p1接收。因此,我们可以说,这个时候,构造函数的this,指向了新的实例对象,p1。
而原型方法上的this就好理解多了,根据上边对函数中this的定义,p1.getName()
中的getName为调用者,他被p1所拥有,因此getName中的this,也是指向了p1。
好啦,我所知道的,关于this的一切,已经总结完了,希望大家在阅读之后,能够真正学到东西,然后给我点个赞_。如果你发现有什么错误,请在评论中指出,我会尽快修改。先谢过了。