JavaScript学习笔记(六)

主要源于廖雪峰老师的JavaScript教程

浏览器

1. 简介

注意IE浏览器,别的支持的都挺好。
IE 6~11:国内用得最多的IE浏览器,历来对W3C标准支持差。从IE10开始支持ES6标准;
另外还要注意识别各种国产浏览器,如某某安全浏览器,某某旋风浏览器,它们只是做了一个壳,其核心调用的是IE,也有号称同时支持IE和Webkit的“双核”浏览器。
不同的浏览器对JavaScript支持的差异主要是,有些API的接口不一样,比如AJAX,File接口。对于ES6标准,不同的浏览器对各个特性支持也不一样。
在编写JavaScript的时候,就要充分考虑到浏览器的差异,尽量让同一份JavaScript代码能运行在不同的浏览器中。

2. 浏览器对象

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

  1. window
    window对象不但充当全局作用域,而且表示浏览器窗口。
    window对象有innerWidthinnerHeight属性,可以获取浏览器窗口的内部宽度和高度。内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。
    兼容性:IE<=8不支持
    相对应,还有一个outerWidthouterHeight属性,可以获取浏览器窗口的整个宽高。
  2. navigator
    navigator对象表示浏览器的信息,最常用的属性包括:
navigator.appName:浏览器名称
navigator.appVersion:浏览器版本
navigator.language:浏览器设置的语言
navigator.platform:操作系统类型
navigator.userAgent:浏览器设定的User-Agent字符串

请注意,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;

  1. screen
    screen对象表示屏幕的信息,常用的属性有:
screen.width: 屏幕宽度,以像素为单位
screen.height:屏幕高度,以像素为单位
screen.colorDepth:返回颜色位数,如8、16、24
  1. location
    location对象表示当前页面的URL信息,例如:一个完成的URL:
    http://www.example.com:8080/path/index.html?a=1&b=2#TOP
    可以用location.href获取。要获取各个部分的值:
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'

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

  1. document
    document对象表示当前页面。由于HTML在浏览器中以DOM形式表示为树形结构,document对象就是整个DOM树的根节点。
    documenttitle属性是从HTML文档中的xxx读取的,但是可以动态改变:
    document.title = '努力学习中...'
    要查找DOM树的某个节点,需要从document对象开始查找。最常用根据ID和Tag Name。
#元数据
摩卡
热摩卡咖啡
酸奶
北京老酸奶
果汁
鲜榨苹果汁

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

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

menu = document.getElementById('drink-menu');
menu.tagName;//'DL'

drinks = document.getElementsByTagName('dt');
s  = '提供的饮料有:';
for (i=0; i

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代码是允许的:



    
        
    
    ...

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

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

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

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

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

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

3. 操作DOM 简介

由于HTML文档被浏览器解析后就是一颗DOM树,要改变HTML结构,就需要通过JavaScript来操作DOM。
操作一个DOM节点实际上就是这么几个操作:

  • 更新:更新该DOM节点的内容,相当于更新了该DOM节点表示的HTML的内容;
  • 遍历:遍历该DOM节点下的子节点,以便进行进一步操作;
  • 添加:在该DOM节点下新增一个子节点,相当于动态增加了一个HTML节点;
  • 删除:将该节点从HTML中删除,相当于删掉了该DOM节点的内容以及它包含的所有子节点。
    那么,如何拿到DOM节点呢:
#ID是唯一的
document.getElementById()
#下面这两个都是返回一组DOM节点。
document.getElementsByTagName()
#CSS的
document.getElementsByClassName()

要精确地选择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;

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

// 通过querySelector获取ID为q1的节点:
var q1 = document.querySelector('#q1');
//通过querySelectorAll获取q1节点内的符合条件的所有节点:
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

  1. 更新DOM
    拿到一个DOM节点后,我们可以对它进行更新。
    可以直接修改节点的文本,有两种方法:

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

//获取

...

var p = document.getElementById('p-id'); //设置文本为abc: p.innerHTML = 'ABC'; //

ABC

//设置HTML p.innerHTML = ' ABC RED XYZ'; //

...

的内部结构已修改

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

-2. 修改innerTexttextContent属性,这样可以自动对字符串进行HTML编码,保证无法设置任何HTML标签:

// 获取

...

var p = document.getElementById('p-id'); // 设置文本: p.innerText = ''; // HTML被自动编码,无法设置一个

这种方式的缺点是扰乱了浏览器对form的正常提交。浏览器默认点击

注意,return true来告诉浏览器继续提交,如果return false,浏览器将不会继续提交form,这种情况通常对应用户输入有误,提示用户错误信息后终止提交form.
在检查和修改时,要充分利用来传递数据。
例如,很多登录表单希望用户输入用户名和口令。但是,安全考虑,提交表单时不传输明文口令,而是口令的MD5。普通JavaScript开发人员会直接修改:


这个做法看上去没啥问题,但用户输入了口令提交时,口令框的显示会突然从几个*变成32个*
要想不改变用户的输入,可以利用实现:


注意:idmd5-password标记了name="password",而用户输入的idinput-password没有name属性。没有name属性的的数据不会被提交。

5. 操作文件

在HTML表单中,可以上传文件的唯一控件就是
注意:当一个表单包含时,表单的enctype必须指定为multipart/form-data,method必须指定为post,浏览器才能正确编码并以multipart/form-data格式发送表单的数据。
出于安全考虑,浏览器只允许用户点击来选择本地文件,用JavaScript对value赋值是没有任何效果的。当用户选择了上传某个文件后,JavaScript也无法获得该文件的真实路径。通常,上传的文件都由后台服务器处理,JavaScript可以在提交表单时对文件扩展名做检查,以防止用户上传无效格式的文件:

var f = document.getElementById('test-file-upload');
var filename = f.value;  //'C:\fakepath\test.png'
if (!filename || !(filename.endsWith('.jpg') || filename.endsWith('.png') || filename.endsWith('.gif'))) {
    alert('Can only upload image file.')
    return false;
}
  1. File API
    由于JavaScript对用户上传的文件操作非常有限,尤其是无法读取文件内容,是的很多需要操作文件的网页不得不用Flash这样的三方插件来实现。
    随着H5的普及,新增的File API允许JavaScript读取文件内容,获得更多的文件信息。
    H5 的File API提供了FileFileReader两个主要对象,可以获得文件信息并读取文件。
    下面的例子演示了如何读取用户选取的图片文件,并在一个
    中预览图像:
var 
        fileInput = document.getElementById('test-image-file'),
        info = document.getElementById('test-file--info'),
        preview = document.getElementById('test-image-preview');
//监听change事件:
fileInput.addEventListener('change', function () {
    //清楚背景图片
    preview.style.backgroundImage = '';
    //检查文件是否选择
    if (!fileInput.value) {
          info.innerHTML = '没有选择文件';
          return;
    }
    //获取File引用
    var file = fileInput.files[0];
    //获取File信息:
    info.innerHTML  = '文件: ' + file.name + '
' + '大小: ' + file.size + '
' + '修改: ' + file.lastModifiedDate; if(file.type !== 'image/jpeg' && file.type !== 'image/png' && file.type !== 'image/gif') { alert('不是有效的图片文件!'); return; } //读取文件 var reader = new FileReader(); reader.onload = function(e) { var data = e.target.result;//'data:image/jpeg;base64,/9j/4AAQSK...(base64编码)...' preview.style.backgroundImage = 'url(' + data + ')'; }; //以DataURL的形式读取文件: reader.readAsDataURL(file); })

上面的代码演示了如何通过H5的File API读取文件的内容。以DataURL的形式读取到的文件是一个字符串,类似于data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...,常用于设置图像。如果需要服务器端处理,把字符串base64,后面的字符发送给服务器并用Base64解码就可以得到原始文件的二进制内容。

  1. 回调
    上面的代码还演示了JavaScript的一个重要特性就是单线程执行的模式。在JavaScript中,浏览器的JavaScript执行引擎在执行JavaScript代码时,总是以单线程模式执行,也就是说,任何时候,JavaScript代码都不可能同时有多余1个线程在执行。
    那么,单线程模式执行的JavaScript,如何处理多任务??
    在JavaScript中,执行多任务实际上都是异步回调,比如上面的代码:
    reader.readAsDataURL(file);
    会发起一个异步操作来读取文件内容。因为是异步操作,所以我们在JavaScript代码中就不知道什么时候操作结束,因此需要先设置一个回调函数:
    reader.onload = function(e){//当文件读取完成后,自动调用此函数};
    当文件读取完成后,JavaScript引擎将自动调用我们设置的回调函数。执行回调函数时,文件已经读取完毕,所以我们可以在回调函数内部安全地获得文件内容。

6. AJAX

AJAX不是JavaScript规范,它只是一个发明的缩写:Asynchronous JavaScript and XML,用JavaScript执行异步网络请求。

如果仔细观察一个Form的提交,你就会发现,一旦用户点击“Submit”按钮,表单开始提交,浏览器就会刷新页面,然后在新页面里告诉你操作是成功了还是失败了。如果不幸由于网络太慢或者其他原因,就会得到一个404页面。
这就是Web的运作原理:一次HTTP请求对应一个页面。

如果要让用户留在当前页面中,同时发出HTTP请求,就必须用JavaScript发送这个新的请求,即受到数据后,再用JavaScript更新页面,这样一来,用户就感觉自己仍然停留在当前页面,但是数据却可以不断更新。
用JavaScript写一个完整的AJAX代码并不复杂,但需要注意:AJAX请求是异步执行的,也就是说,要通过回调函数获得响应。

在现在浏览器上写AJAX主要依靠XMLHttpRequest对象:

'use strict';
function success(text) {
      var textarea = document.getElementById('test-response-text');
      textarea.value = text;
}

function fail(code) {
    var textarea = document.getElementById('test-response-text');
    textarea.value = 'Error code: ' + code;
}

//低版本IE,需要换成ActiveXObject对象来创建
var request;
if(window.XMLHttpRequeest) {
      request = new XMLHttpRequest();  //新建XMLHttpRequest对象
} else {
      request = new ActiveXObject('Microsoft.XMLHTTP');//低版本IE用这个创建ActiveXObject对象
}

request.onreadystatechange = function () {
//状态发生变化时,函数被回调
      if (request.readyState === 4) {
      //成功完成
            if (request.status === 200) {
            //成功,通过responseText拿到相应的文本:
                    return success(request.responseText);
            } else {
            //失败,根据响应码判断失败原因:
                    return fail(request.status);
            }
      } else {
      //HTTP请求还在继续
      }
}

//发送请求
request.open('GET', '/api/categories');
request.send();

alert('请求已发送,请等待响应...');

XMLHttpRequest对象的open()方法有3个参数,第一个参数指定是GET还是POST,第二个参数指定URL 地址,第三个参数指定是否使用异步,默认是true,所以不用写。
注意:千万不要把第三个参数指定为false,否则浏览器将停止响应,知道AJAX请求完成。如果这个请求耗时10秒,那么10秒内你会发现浏览器处于假死状态。
最后调用send()方法才真正发送请求。GET请求不需要参数,POST请求需要把body部分以字符串或者FormData对象传进去。

  1. 安全限制
    上面open中的URL使用的是相对路径。如果你把它改为http://www.sina.com.cn/,在运行,肯定报错。这是因为浏览器的同源策略导致的。默认情况下,JavaScript在发送AJAX请求时,URL的域名必须和当前页面完全一致。

完全一致的意思是,域名要相同(www.example.comexample.com不同),协议要相同(httphttps不同),端口号要相同(默认是:80端口,它和:8080就不同)。有的浏览器口子松一点,允许端口不同,大多数浏览器都会严格遵守这个限制。

JavaScript如何请求外域的URL了呢?
一是通过Flash插件发送HTTP请求,这种方法可以绕过浏览器的安全限制,但是必须安装Flash,并跟Flash交互。
二是通过在同源域名下架设一个代理服务器来转发,JavaScript负责把请求发送到代理服务器:
/proxy?url=http://www.sina.com.cn
代理服务器再把结果返回,这样就遵守了浏览器的同源策略。这种方式麻烦之处在于需要服务器额外做开发。
第三种方式称为JSONP,它有个限制,只能用GET请求,并且要求返回JavaScript。这种方式跨域实际上利用了浏览器允许跨域引用JavaScript资源:



    
    ...


...


JSONP通常以函数调用的形式返回,例如,返回JavaScript内容如下:
foo('data');
这样一来,我们如果在页面中先准备好了foo()函数,然后给页面动态加一个

你可能感兴趣的:(JavaScript学习笔记(六))