由于JavaScript的出现就是为了能在浏览器中运行,所以,浏览器自然是JavaScript开发者必须要关注的。

目前主流的浏览器分这么几种:

  • IE 6~11:国内用得最多的IE浏览器,历来对W3C标准支持差。从IE10开始支持ES6标准
  • Chrome:Google出品的基于Webkit内核浏览器,内置了非常强悍的JavaScript引擎——V8。由于Chrome一经安装就时刻保持自升级,所以不用管它的版本,最新版早就支持ES6了;
  • Safari:Apple的Mac系统自带的基于Webkit内核的浏览器,从OS X 10.7 Lion自带的6.1版本开始支持ES6,目前最新的OS X 10.11 El Capitan自带的Safari版本是9.x,早已支持ES6;
  • Firefox:Mozilla自己研制的Gecko内核和JavaScript引擎OdinMonkey早期的Firefox按版本发布,后来终于聪明地学习Chrome的做法进行自升级,时刻保持最新
  • 移动设备上目前iOS和Android两大阵营分别主要使用Apple的Safari和Google的Chrome,由于两者都是Webkit核心,结果HTML5首先在手机上全面普及(桌面绝对是Microsoft拖了后腿),对JavaScript的标准支持也很好,最新版本均支持ES6。

其他浏览器如Opera等由于市场份额太小就被自动忽略了。

另外还要注意识别各种国产浏览器,如某某安全浏览器,某某旋风浏览器,它们只是做了一个壳,其核心调用的是IE,也有号称同时支持IE和Webkit的“双核”浏览器。

不同的浏览器对JavaScript支持的差异主要是,有些API的接口不一样,比如AJAX,File接口。对于ES6标准,不同的浏览器对各个特性支持也不一样。

在编写JavaScript的时候,就要充分考虑到浏览器的差异,尽量让同一份JavaScript代码能运行在不同的浏览器中。

浏览器对象

JavaScript可以获取浏览器提供的很多对象,并进行操作。

window

window对象不但充当全局作用域,而且表示浏览器窗口。

window对象有innerWidthinnerHeight属性,可以获取浏览器窗口的内部宽度和高度。内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。

兼容性:IE<=8不支持。

'use strict';
// 可以调整浏览器窗口大小试试:
console.log('window inner size: ' + window.innerWidth + ' x ' + window.innerHeight);
undefined
window inner size: 888 x 599
<eval>/VM46947637:3

对应的,还有一个outerWidthouterHeight属性,可以获取浏览器窗口的整个宽高。

navigator

navigator对象表示浏览器的信息,最常用的属性包括:

  • navigator.appName:浏览器名称;
  • navigator.appVersion:浏览器版本;
  • navigator.language:浏览器设置的语言;
  • navigator.platform:操作系统类型;
  • navigator.userAgent:浏览器设定的User-Agent字符串。
'use strict';

console.log('appName = ' + navigator.appName);
console.log('appVersion = ' + navigator.appVersion);
console.log('language = ' + navigator.language);
console.log('platform = ' + navigator.platform);
console.log('userAgent = ' + navigator.userAgent);
appName = Netscape
<eval>/VM46947639:3
appVersion = 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
<eval>/VM46947639:4
language = zh-CN
<eval>/VM46947639:5
platform = Win32
<eval>/VM46947639:6
userAgent = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
<eval>/VM46947639:7
undefined

美国Netscape公司,以开发Internet浏览器闻名。网景是网景通信公司(Netscape Communications Corporation)的常用简称。网景通信公司曾经是一家美国的计算机服务公司,以其生产的同名网页浏览器[Netscape Navigator](https://baike.baidu.com/item/Netscape Navigator/1014148)而闻名。1998年11月,网景被美国在线(AOL)收购。

Chrome/75.0.3770.100

请注意navigator的信息可以很容易地被用户修改,所以JavaScript读取的值不一定是正确的。很多初学者为了针对不同浏览器编写不同的代码,喜欢用if判断浏览器版本,例如:

var width;
if (getIEVersion(navigator.userAgent) < 9) {
    width = document.body.clientWidth;
} else {
    width = window.innerWidth;
}

但这样既可能判断不准确,也很难维护代码。正确的方法是充分利用JavaScript对不存在属性返回undefined的特性,直接用短路运算符||计算

var width = window.innerWidth || document.body.clientWidth;

screen

screen对象表示屏幕的信息,常用的属性有:

  • screen.width:屏幕宽度,以像素为单位;
  • screen.height:屏幕高度,以像素为单位;
  • screen.colorDepth:返回颜色位数,如8、16、24。
'use strict';
console.log('Screen size = ' + screen.width + ' x ' + screen.height);
console.log('Screen color depth = ' + screen.colorDepth);
console.log('window inner size: ' + window.innerWidth + ' x ' + window.innerHeight);
undefined
Screen size = 1536 x 864
<eval>/VM46947640:2
Screen color depth = 24
<eval>/VM46947640:3
window inner size: 902 x 599
<eval>/VM46947640:4


浏览器 JavaScript 支持版本检测 哪些浏览器支持javascript_HTML

其中关系:

1920/1.25
1536
1080/1.25
864
screen;  //展开
Screen {availWidth: 1462, availHeight: 864, width: 1536, height: 864, colorDepth: 24, …}
availHeight:864
availLeft:0
availTop:0
availWidth:1462
colorDepth:24
height:864
orientation:ScreenOrientation {angle: 0, type: 'landscape-primary', onchange: null}
pixelDepth:24
width:1536
__proto__:Screen

location

location对象表示当前页面的URL信息。例如,一个完整的URL:

http://www.example.com:8080/path/index.html?a=1&b=2#TOP

可以用location.href获取。要获得URL各个部分的值,可以这么写:

location.protocol; // 'http'
location.host; // 'www.example.com'
location.port; // '8080'
location.pathname; // '/path/index.html'
location.search; // '?a=1&b=2'
location.hash; // 'TOP'

protocol 英[ˈprəʊtəkɒl]
n. 礼仪; (外交条约的)草案; (数据传递的)协议; 科学实验报告(或计划)

要加载一个新页面,可以调用location.assign()。如果要重新加载当前页面,调用location.reload()方法非常方便。

'use strict';

if (confirm('重新加载当前页' + location.href + '?')) {
    location.reload();
} else {
    location.assign('/'); // 回根路径;可设置一个新的URL地址
}

document

document对象表示当前页面。由于HTML在浏览器中以DOM形式表示为树形结构,document对象就是整个DOM树的根节点

documenttitle属性是从HTML文档中的<title>xxx</title>读取的,但是可以动态改变:

'use strict';

document.title = '努力学习JavaScript!';

网页Tab上的Title马上就变了!

要查找DOM树的某个节点,需要从document对象开始查找。最常用的查找是根据ID和Tag Name。

我们先准备HTML数据:

<style type="text/css">
    dd.important {color:purple;}
</style>

<dl id="drink-menu" style="border:solid 1px #ccc;padding:6px;">
    <dt>摩卡</dt>
    <dd>热摩卡咖啡</dd>
    <dt>酸奶</dt>
    <dd>北京老酸奶</dd>
    <dt>果汁</dt>
    <dd class="important">鲜榨苹果汁</dd>
    <dd class="important">kiwi汁</dd>
    <dd>Orange Juice</dd>
</dl>


浏览器 JavaScript 支持版本检测 哪些浏览器支持javascript_XML_02

document对象提供的getElementById()getElementsByTagName()可以按ID获得一个DOM节点和按Tag名称获得一组DOM节点:

'use strict';

var menu = document.getElementById('drink-menu');
var drinks = document.getElementsByTagName('dt');
var i, s;

s = '提供的饮料有:';
for (i=0; i<drinks.length; i++) {
    s = s + drinks[i].innerHTML + ',';
}
console.log(s);
VM18:11 提供的饮料有:摩卡,酸奶,果汁,
undefined

document对象还有一个cookie属性,可以获取当前页面的Cookie。

Cookie是由服务器发送的key-value标示符。因为HTTP协议是无状态的,但是服务器要区分到底是哪个用户发过来的请求,就可以用Cookie来区分。当一个用户成功登录后,服务器发送一个Cookie给浏览器,例如user=ABC123XYZ(加密的字符串)...,此后,浏览器访问该网站时,会在请求头附上这个Cookie,服务器根据Cookie即可区分出用户。

Cookie还可以存储网站的一些设置,例如,页面显示的语言等等。

JavaScript可以通过document.cookie读取到当前页面的Cookie:

document.cookie; // 'v=123; remember=true; prefer=zh'

由于JavaScript能读取到页面的Cookie,而用户的登录信息通常也存在Cookie中,这就造成了巨大的安全隐患,这是因为在HTML页面中引入第三方的JavaScript代码是允许的:

<!-- 当前页面在wwwexample.com -->
<html>
    <head>
        <script src="http://www.foo.com/jquery.js"></script>
    </head>
    ...
</html>

如果引入的第三方的JavaScript中存在恶意代码,则www.foo.com网站将直接获取到www.example.com网站的用户登录信息。

为了解决这个问题,服务器在设置Cookie时可以使用httpOnly,设定了httpOnly的Cookie将不能被JavaScript读取。这个行为由浏览器实现,主流浏览器均支持httpOnly选项,IE从IE6 SP1开始支持。

为了确保安全,服务器端在设置Cookie时,应该始终坚持使用httpOnly

history

history对象保存了浏览器的历史记录,JavaScript可以调用history对象的back()forward (),相当于用户点击了浏览器的“后退”或“前进”按钮。

这个对象属于历史遗留对象,对于现代Web页面来说,由于大量使用AJAX和页面交互,简单粗暴地调用history.back()可能会让用户感到非常愤怒。

新手开始设计Web页面时喜欢在登录页登录成功时调用history.back(),试图回到登录前的页面。这是一种错误的方法。

任何情况,你都不应该使用history这个对象了。

操作DOM

DOM百度百科

文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展置标语言的标准编程接口。它是一种与平台和语言无关的应用程序接口(API),它可以动态地访问程序和脚本,更新其内容、结构和www文档的风格(目前,HTML和XML文档是通过说明部分定义的)。文档可以进一步被处理,处理的结果可以加入到当前的页面。DOM是一种基于树的API文档,它要求在处理过程中整个文档都表示在存储器中。另外一种简单的API是基于事件的SAX,它可以用于处理很大的XML文档,由于大,所以不适合全部放在存储器中处理。

Document Object Model的历史可以追溯至1990年代后期微软与Netscape的“浏览器大战” [3] (browser wars),双方为了在JavaScript与JScript一决生死,于是大规模的赋予浏览器强大的功能。微软在网页技术上加入了不少专属事物,计有VBScript、ActiveX、以及微软自家的DHTML格式等,使不少网页使用非微软平台及浏览器无法正常显示。DOM即是当时蕴酿出来的杰作。
DOM分为HTML DOM和XML DOM两种。它们分别定义了访问和操作HTML/XML文档的标准方法,并将对应的文档呈现为带有元素、属性和文本的树结构(节点树)。
1)DOM树定义了HTML/XML文档的逻辑结构,给出了一种应用程序访问和处理XML文档的方法。
2)在DOM树中,有一个根节点,所有其他的节点都是根节点的后代。
3)在应用过程中,基于DOM的HTML/XML分析器将一个HTML/XML文档转换成一棵DOM树,应用程序通过对DOM树的操作,来实现对HTML/XML文档数据的操作。

由于HTML文档被浏览器解析后就是一棵DOM树,要改变HTML的结构,就需要通过JavaScript来操作DOM。

始终记住DOM是一个树形结构。操作一个DOM节点实际上就是这么几个操作:

  • 更新:更新该DOM节点的内容,相当于更新了该DOM节点表示的HTML的内容;
  • 遍历:遍历该DOM节点下的子节点,以便进行进一步操作;
  • 添加:在该DOM节点下新增一个子节点,相当于动态增加了一个HTML节点;
  • 删除:将该节点从HTML中删除,相当于删掉了该DOM节点的内容以及它包含的所有子节点

在操作一个DOM节点前,我们需要通过各种方式先拿到这个DOM节点。最常用的方法是document.getElementById()document.getElementsByTagName(),以及CSS选择器document.getElementsByClassName()

由于ID在HTML文档中是唯一的,所以document.getElementById()可以直接定位唯一的一个DOM节点。document.getElementsByTagName()document.getElementsByClassName()总是返回一组DOM节点。要精确地选择DOM,可以先定位父节点,再从父节点开始选择,以缩小范围。

例如:

// 返回ID为'test'的节点:
var test = document.getElementById('test');

// 先定位ID为'test-table'的节点,再返回其内部所有tr节点:
var trs = document.getElementById('test-table').getElementsByTagName('tr');

// 先定位ID为'test-div'的节点,再返回其内部所有class包含red的节点:
var reds = document.getElementById('test-div').getElementsByClassName('red');

// 获取节点test下的所有直属子节点:
var cs = test.children;

// 获取节点test下第一个、最后一个子节点:
var first = test.firstElementChild;
var last = test.lastElementChild;

实操:

var dl = document.getElementById('drink-menu');
undefined
dl;
<dl id="drink-menu" style="border:solid 1px #ccc;padding:6px;">…</dl><dt>摩卡</dt><dd>热摩卡咖啡</dd><dt>酸奶</dt><dd>北京老酸奶</dd><dt>果汁</dt><dd>鲜榨苹果汁</dd><dd>kiwi汁</dd><dd>Orange Juice</dd></dl>
var dt = document.getElementById('drink-menu').getElementsByTagName('dt');
undefined
dt;
HTMLCollection(3) [dt, dt, dt]
var imp = document.getElementById('drink-menu').getElementsByClassName('important');
undefined
typeof imp;
"object"
imp;
HTMLCollection(2) [dd.important, dd.important]0: dd.important1: dd.importantlength: 2__proto__: HTMLCollection
Array.isArray(imp);
false
imp[0];
<dd class="important">鲜榨苹果汁</dd>
imp[0].innerText;
"鲜榨苹果汁"
var dl = document.getElementById('drink-menu');
undefined
var dt = dl.children;
undefined
dt;
HTMLCollection(8) [dt, dd, dt, dd, dt, dd.important, dd.important, dd]0: dt1: dd2: dt3: dd4: dt5: dd.important6: dd.important7: ddaccessKey: ""assignedSlot: nullattributeStyleMap: StylePropertyMap ...

var last = dl.lastElementChild;
undefined
last;
<dd>Orange Juice</dd>

第二种方法是使用querySelector()querySelectorAll(),需要了解selector语法,然后使用条件来获取节点,更加方便:

// 通过querySelector获取ID为q1的节点:
var q1 = document.querySelector('#q1');

// 通过querySelectorAll获取q1节点内的符合条件(拥有highlighted类样式的div元素标签 的子元素p标签)的所有节点:
var ps = q1.querySelectorAll('div.highlighted > p');

注意:低版本的IE<8不支持querySelectorquerySelectorAll。IE8仅有限支持。

严格地讲,我们这里的DOM节点是指Element,但是DOM节点实际上是Node,在HTML中,Node包括ElementCommentCDATA_SECTION等很多种,以及根节点Document类型,但是,绝大多数时候我们只关心Element,也就是实际控制页面结构的Node,其他类型的Node忽略即可。根节点Document已经自动绑定为全局变量document

练习

如下的HTML结构:

JavaScript

Java

Python

Ruby

Swift

Scheme

Haskell

<!-- HTML结构 -->
<div id="test-div">
<div class="c-red">
    <p id="test-p">JavaScript</p>
    <p>Java</p>
  </div>
  <div class="c-red c-green">
    <p>Python</p>
    <p>Ruby</p>
    <p>Swift</p>
  </div>
  <div class="c-green">
    <p>Scheme</p>
    <p>Haskell</p>
  </div>
</div>

请选择出指定条件的节点:

'use strict';
// 选择<p>JavaScript</p>:
var js = document.querySelector('#test-p');
for(let key in js) {
    if(js[key] === 'JavaScript') {
        console.log(key + '---' + js[key])
    }
}
undefined
innerText---JavaScript
<eval>/VM46947632:3
outerText---JavaScript
<eval>/VM46947632:3
innerHTML---JavaScript
<eval>/VM46947632:3
textContent---JavaScript
<eval>/VM46947632:3

// 选择<p>Python</p>,<p>Ruby</p>,<p>Swift</p>:
var arr = document.querySelectorAll('div.c-red.c-green p');

// 选择<p>Haskell</p>:
var haskell = document.querySelectorAll('div.c-green')[1].querySelectorAll('p')[1];
undefined
haskell.innerText;
'Haskell'

// 测试:
if (!js || js.innerText !== 'JavaScript') {
    alert('选择JavaScript失败!');
} else if (!arr || arr.length !== 3 || !arr[0] || !arr[1] || !arr[2] || arr[0].innerText !== 'Python' || arr[1].innerText !== 'Ruby' || arr[2].innerText !== 'Swift') {
    console.log('选择Python,Ruby,Swift失败!');
} else if (!haskell || haskell.innerText !== 'Haskell') {
    console.log('选择Haskell失败!');
} else {
    console.log('测试通过!');
}
测试通过!
<eval>/VM46947644:9
undefined

高级方法:

var haskell = document.querySelector('.c-green.c-red+.c-green p:nth-of-type(2)');
undefined
haskell.innerText;
'Haskell'

更新DOM

拿到一个DOM节点后,我们可以对它进行更新。

修改innerHTML innerText textContent属性

可以直接修改节点的文本,方法有两种:

一种是修改innerHTML属性,这个方式非常强大,不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树:

// 获取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 设置文本为abc:
p.innerHTML = 'ABC'; // <p id="p-id">ABC</p>
// 设置HTML:
p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';
// <p>...</p>的内部结构已修改

innerHTML时要注意,是否需要写入HTML。如果写入的字符串是通过网络拿到的,要注意对字符编码来避免XSS攻击。

XSS攻击:XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但实际上也可以包括Java、 VBScript、ActiveX、 Flash 或者甚至是普通的HTML。攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容。

百度百科

第二种是修改innerTexttextContent属性,这样可以自动对字符串进行HTML编码,保证无法设置任何HTML标签

// 获取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 设置文本:
p.innerText = '<script>alert("Hi")</script>';
// HTML被自动编码,无法设置一个<script>节点:
// <p id="p-id"><script>alert("Hi")</script></p>

innerText textContent区别

两者的区别在于读取属性时,innerText不返回隐藏元素的文本,而textContent返回所有文本。另外注意IE<9不支持textContent

<body>
  ...
  <p id="p-id">...</p>

  <div class="test" id="test">
    test
    <div style="display:none">
      <span>
          test
      </span>
    </div>
    <style>
      hi
    </style>
    <p>
      p hi
    </p>
  </div>


</body>

innerText与网页显示内容一致。


浏览器 JavaScript 支持版本检测 哪些浏览器支持javascript_HTML_03

输出测试如下,区别明显:

var testDiv = document.getElementById('test');
console.log("textContent输出:"+testDiv.textContent);
console.log("innerText输出:"+testDiv.innerText);
undefined
textContent输出:
    test
    
      
          test
      
    
    
      hi
    
    
      p hi
    
  
<eval>/VM46947590:2
innerText输出:test

p hi

修改CSS

修改CSS也是经常需要的操作。DOM节点的style属性对应所有的CSS,可以直接获取或设置。因为CSS允许font-size这样的名称,但它并非JavaScript有效的属性名,所以需要在JavaScript中改写为驼峰式命名fontSize

var a-b;
Uncaught SyntaxError: Unexpected token -

// 获取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 设置CSS:
p.style.color = '#ff0000';
p.style.fontSize = '20px';
p.style.paddingTop = '2em';

练习

有如下的HTML结构:

javascript

Java

<!-- HTML结构 -->
<div id="test-div">
  <p id="test-js">javascript</p>
  <p>Java</p>
</div>

请尝试获取指定节点并修改:

'use strict';

// 获取<p>javascript</p>节点:
var js = document.getElementById('test-js');
undefined
// 修改文本为JavaScript:
js.innerText='JavaScript';
'JavaScript'
// 修改CSS为: color: #ff0000, font-weight: bold
js.style.color='#ff0000';
js.style.fontWeight='bold';
'bold'

js.style.color
'rgb(255, 0, 0)'
typeof js.style.color
'string'

// 测试:
if (js && js.parentNode && js.parentNode.id === 'test-div' && js.id === 'test-js') {
    if (js.innerText === 'JavaScript') {
        if (js.style && js.style.fontWeight === 'bold' && (js.style.color === 'red' || js.style.color === '#ff0000' || js.style.color === '#f00' || js.style.color === 'rgb(255, 0, 0)')) {
            console.log('测试通过!');
        } else {
            console.log('CSS样式测试失败!');
        }
    } else {
        console.log('文本测试失败!');
    }
} else {
    console.log('节点测试失败!');
}
undefined
测试通过!
<eval>/VM46947596:5