常说的JavaScript包含两部分:
- JavaScript基础知识(ECMA262标准)
- JS-Web-API(W3C标准)
JavaScript基础知识
变量类型
undefined
boolean
string
number
function
object
-
值类型:各个类型都可以明确区分
console.log(typeof undefined)//undefined console.log(typeof true)//boolean console.log(typeof 'abc')//string console.log(typeof 123)//number //console.log(typeof 123.456)//number
-
引用类型:无法明确区分类型,因为函数比较特殊,只能将函数区分出来
console.log(typeof console.log)//function console.log(typeof [])//object console.log(typeof {})//object console.log(typeof null)//object
类型转换
JavaScript是弱语言,存在隐式类型转换。
a = 100+10//110
b = 100+'10'//'10010'
双等与三等
双等只比较二者的值,且存在隐式类型转换;而三等不仅比较二者的值,还会比较它们的类型。
慎用双等,如下所示,表达式100=='100'
中'100'
被转换成了数字100…
console.log(100 == '100')//true,100->'100'
console.log(0 == '')//true,0->false;''->false
console.log(null == undefined) //true, null,undefined->false
内置函数与对象
基本内置函数
Object
Array
String
Number
RegExp
Date
Boolean
Function
Error
对象
Math
JSON
构造函数
Q:new一个对象的过程?
A:创建一个对象;this指向这个对象;执行代码,为this赋值;返回this。
//构造函数
function Foo(name, age){
//this一开始是一个空对象
this.name = name
this.age = age
return this//默认有这一行
}
我们可以通过构造函数定义不同的变量:
var f1 = new Foo('张三', 20)
var f2 = new Foo('李四', 20)
构造函数——扩展
很多对象的构造方法可以简写,如下:
var a = {}//var a = new Object()
var b = []//var b = new Array()
function foo(){}//var Foo = new Function()
Q:怎么判断一个函数是否是一个变量的构造函数?
A:使用instanceof
var a = {}
var b = []
console.log(b instanceof Array)//true
console.log(b instanceof Object)//true
原链规则
- 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(
null
除外);
array = []
array.x = 1
obj = Object()
obj.x = 2
function f(){}
f.x = 3
- 所有的引用类型(数组、对象、函数),都有一个
__proto__
属性(隐式原型),属性值是一个普通的对象;
console.log(obj.__proto__)
console.log(array.__proto__)
console.log(f.__proto__)
-
所有的函数,都有一个
protopyte
属性(显式原型),属性值也是一个普通的对象; -
所有的引用类型(数组、对象、函数),
__proto__
属性值指向它的构造函数的prototype
属性值;
console.log(obj.__proto__ === Object.prototype)//true
- 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的
__proto__
(即构造函数的prototype
)中查找。
function Hello(name){
this.name = name
}
Hello.prototype.PrintName = function(){
console.log(this.name)
}
h = new Hello('tom')
h.PrintName()//tom
原型链
如下代码,h = new Hello('tom')
console.log(h.toString())
查找链为:h.__proto__
.__proto__
- h对象没有toString属性,根据第五条原型规则,它会去它的
__proto__
属性中查找,h的构造函数是Hello,所以h.__proto__
等于Hello.prototype
; Hello.prototype
也是一个对象,也没有toString属性,所以又会去它的__proto__
中查找;Hello.prototype
的构造函数是Object
, 所以Hello.prototype.__proto__
等于Object.prototype
;- 特例(js为避免死循环):
Object.prototype.__proto__
等于null
。
异步与单线程
JavaScript是单线程执行,所以异步非常重要。那么什么时候会需要异步呢?答案是可能发生等待的情况,且等待的时候不能阻塞。
Q:异步与同步的区别。
A:同步会阻塞代码执行,而异步不会;alert
是同步,setTimeout
是异步。
异步的使用场景
- 定时任务:
setTimeout
,setInverval
; - 网络请求:ajax请求,动态
<img>
加载; - 事件绑定
定时任务
示例1:
console.log(100)
setTimeout(function(){
console.log(200)
})
console.log(300)
//100 300 200
解析过程:
- 执行第一行,打印100
- 执行
setTimeout
后,传入setTimeout
的函数会被暂存起来,不会立即执行;即setTimeout
是个异步,它会被放在一边(先等着,啥时候有空啥时候执行); - 执行最后一行,打印300;
- 待所有程序执行完,处于空闲状态时,会立马看有没有暂存起来的任务要执行;
- 发现
setTimeout
中的函数无需等待时间,立即过来执行:打印200。
示例2:
console.log(100)
setTimeout(function(){
console.log(200)
}, 1000)
console.log(300)
//100 300(1s后) 200
解析过程:
- 执行第一行,打印100;
- 执行
setTimeout
后,传入setTimeout
的函数会被暂存起来,不会立即执行;因为设置了暂存时间,所有会在1s(1000ms)后解封; - 执行最后一行,打印300;
- 待所有程序执行完,处于空闲状态时,会立马看有没有暂存起来的任务要执行;
- 发现
setTimeout
中的函数处于封闭状态,等待函数解封后再执行:打印200。
网络请求
示例1:ajax请求
console.log('start')
$.get('https://www.baidu.com/', function(data){
console.log(data)
})
console.log('end')
//start end data
示例2:动态<img>
加载
console.log('start')
var img = document.createElement('img')
img.onload = function(){
console.log('loaded')
}
img.src = 'x.png'
console.log('end')
//start end loaded
事件绑定
console.log('start')
document.getElementById('js-qa-wenda').addEventListener('click', function(){
alert('clicked')
})
console.log('end')
//start end (点击后)clicked
作用域与闭包
执行上下文
Q:什么是变量提升?
A:变量定义,函数声明(函数表达式不会)会被提前(即提前至代码首部声明)。
- 范围:一段script代码或一个函数;
- 全局:变量定义,函数声明;
- 函数:变量定义,函数声明,this,arguments。
示例:如下是一段script代码;
<script>
console.log(a)//undefined
var a = 100
func('tom')//tom 20
function func(name){
age = 20
console.log(name, age)
var age
}
</script>
全局:在执行第一行之前,执行上下文会将代码块中的变量定义,并赋值为undefined
;并把函数声明提前。
函数:变量定义并赋值为undefined
,并把this
,arguments
提前。
作用域
JavaScript没有块级作用域,只有全局和函数作用域;函数作用域不会被函数外部污染,而全局作用域存在变量被覆盖的威胁。
函数与全局作用域
var a=100
function func1(){
var a = 200
console.log('func',a)
}
console.log('global',a)//global 100
func()//func 200
自由变量
自由变量:当前作用域没有定义的变量。
var a = 100
function func2(){
var b=200
console.log(a)//自由变量绑定:100
console.log(b)//200
}
func2()
闭包
在JavaScript中,闭包的使用场景有:
- 函数作为返回值;
- 函数作为参数传递。
function F1(){
var a=100
return function(){
console.log(a)
}
}
var f1 = F1()
function F2(fn){
var a=200
fn()
}
F2(f1)//100
this的使用场景
- 作为构造函数执行;
- 作为对象属性执行;
- 作为普通函数执行;
- call apply bind。
示例:call apply bind
//call apply bind
function func3(name){
console.log(name)
console.log(this)
}
func3.call({x:100}, 'tom')
func3.call
的含义是执行func3
函数,并把{x:100}
作为this
,后面的参数作为func3
的参数。
拓展
当函数无定义体时,this全等于window。
function fn(){
console.log(this)//this === window
}
fn()
数组API
- forEach:遍历所有元素
- every:判断所有元素是否都符合条件
- some:判断是否至少有一个元素符合条件
- sort:排序
- map:对元素重新组装,生成新数组
- filter:过滤符合条件的元素
forEach
var arr = [1,2,3]
arr.forEach(function(item, index){
console.log(index, item)
})
//0 1
//1 2
//2 3
every
var arr = [1,2,3]
var result1 = arr.every(function(item, index){
if(item < 2){
return true
}
})
console.log(result1)//false
some
var arr = [1,2,3]
var result1 = arr.some(function(item, index){
if(item < 2){
return true
}
})
console.log(result1)//true
sort
var arr1 = [1,4,2,3,5]
var arr2 = arr1.sort(function(a,b){
//从小到大
return a-b
//从大到小
//return b-a
})
console.log(arr2)//[1,2,3,4,5]
map
var arr3 = [1,2,3,4]
var arr4 = arr3.map(function(item,index){
return '<b>'+item+'</b>'
})
console.log(arr4)
//["<b>1</b>", "<b>2</b>", "<b>3</b>", "<b>4</b>"]
filter
var arr5 = [1,2,3]
var arr6 = arr5.filter(function(item, index){
if(item%2===1){
return true
}
})
console.log(arr6)//[1,3]
对象API
var obj = {
x: 100,
y: 200,
z: 300,
}
for(var key in obj){
//原生属性
if(obj.hasOwnProperty(key)){
console.log(key, obj[key])
}
}
//x 100
//y 200
//z 300
JS-Web-API
DOM操作
DOM(Document Object Model),文档对象模型;DOM是一种树型结构,它的本质是HTML结构化(浏览器拿到html代码后负责结构化)之后浏览器和JS可识别的模型;
DOM的常用API有:
- 获取DOM节点,以及节点的property和Attribute;
- 获取父/子节点;
- 新增/删除节点。
Q:DOM节点的attr和property有何区别?
A:property是JS对象的属性(获取和修改);Attribute是html标签的属性(获取和修改)。
示例1:获取DOM节点
var div1 = document.getElementById('div1')//元素
var divlist = document.getElementsByTagName('div')//集合
var containerList = document.getElementsByClassName('.container')//集合
示例2:property(JS对象属性),这些属性是W3C标准规定的。
var pList = document.querySelectorAll('p')//集合
var p = pList[0]//js对象
console.log(p.style.width)//获取样式
p.style.width = '100px'//修改样式
console.log(p.className)//获取class
p.className = 'p1'//修改class
//获取nodeName和nodeType
console.log(p.nodeName)
console.log(p.nodeType)
示例3:Attribute(html标签的属性),如<img data-origin="x.com" src="x.png" style="display:inline">
,属性有data-origin,src,style。
var pList = document.querySelectorAll('p')
var p = pList[0]
p.getAttribute('data-name')
p.setAttribute('data-name', 'imooc')
p.getAttribute('style')
p.setAttribute('style', 'font-size:30px')
示例4:新增/删除节点,获取父/子元素。
//新增节点
var div1 = document.getElementById('div1')
var p1 = document.createElement('p')//添加新节点
p1.innerHTML = 'this is p1'
div1.appendChild('p1')//添加新创建的元素
//获取父元素和子元素
var div1 = document.getElementById('div1')
var parent = div1.parentElement
var child = div1.childNodes
div1.removeChild(child[0])//删除子节点
检测浏览器类型及拆解URL
- navigator:浏览器(拥有浏览器操作的一些属性)
- screen:屏幕(获取屏幕的一些属性,如宽和高)
- location:地址(获取地址栏的一些信息)
- history:历史(包括前进、后退等)
navigator
//navigator:判断浏览器是否为Chrome
var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome')
console.log(isChrome)
screen
//screen:获取屏幕的宽和高
console.log(screen.width)
console.log(screen.height)
location
//location:对地址栏url的操作
console.log(location.href)//地址栏地址
console.log(location.protocol)//传输协议:http or https
console.log(location.pathname)// 路径名(不包括根),如'/poet/1'
console.log(location.screen)//查询字符
console.log(location.hash)//url中#号后面的就是hash
history
//history:浏览器前进和后退
history.back()//后退
history.forward()//前进
URI编/解码
url = "https://www.baidu.com/?search=你好牛逼"
en_url = encodeURI(url)
console.log(en_url);
//https://www.baidu.com/?search=%E4%BD%A0%E5%A5%BD%E7%89%9B%E9%80%BC
console.log(decodeURI(en_url));
//https://www.baidu.com/?search=你好牛逼
ajax
XMLHttpRequest
无论多复杂的ajax封装,都是基于以下形式而做的:
var xhr = new XMLHttpRequest()//定义xhr对象
xhr.open("GET", "/api", false)//以异步(false为异步,true代表同步)的形式,GET请求访问'/api'页面
xhr.onreadystatechange = function(){
//此函数是异步执行的
//每一次readState变化都会触发此函数执行
if(xhr.readyState == 4){
if(xhr.status == 200){
alert(xhr.responseText)
}
}
}
xhr.send(null)//发送
发送(send)之后会随时监听状态(xhr.readyState
)变化,每发生一次变化就调用一次onreadystatechange
。
注意:IE低版本使用ActiveXObject,与W3C标准不符;建议忽略。
readyState
readyState是xhr发送请求的状态码,而status是HTTP标准状态码。
- 0:未初始化(还没有调用send()方法)
- 1:载入(已调用send()方法,正在发送请求)
- 2:载入完成(send()方法执行完成,已经接收到全部响应内容)
- 3:交互(正在解析响应内容)
- 4:完成(响应内容解析完成,可以在客户端调用了)
跨域
Q:什么是跨域?
A:
浏览器有同源策略,不允许ajax访问其他域接口;
跨域条件:协议、域名、端口,有一个不同就算跨域。
跨域注意事项:
- 所有的跨域请求都必须经过信息提供方允许;
- 如果未经允许即可获取,那是浏览器同源策略出现漏洞。
可以跨域的三个标签(ajax不允许):
标签 | 示例 |
---|---|
img | <img src=xxx.img> |
css | <link href=xxx.css> |
js | <script src=xxx.js> |
例如你的网站要跨域访问慕课网的一个接口,慕课网给你一个地址http://coding.m.imooc.api.js,返回内容格式如callback({x:100, y:200})
(可动态生成)。如下代码:以下代码跨域返回的数据都会被callback接收。
<script>
window.callback = function(data){
//跨域得到的信息
console.log(data)
}
</script>
<script src="http://coding.m.imooc.com/api.js" ></script>
Cookie
cookie本身用于客户端和服务器端通信,但是它有本地存储的功能,于是就被“借用”,使用document.cookie
即可获取和修改。cookie存储的缺点有:
- 存储量太小,只有4kb;
- 所有http请求都会带上cookie,会影响获取资源的效率;
- API简单,需要封装才能用(cookie中包含多类信息,如账号,密码等)。
localStorage与sessionStorage
localStorage与sessionStorage是HTML5专门为存储而设计,它们都存储在本地,最大容量为5M,且API简洁易用
localStorage.setItem(key, value)
localStorage.getItem(key)
sessionStorage.setItem(key, value)
sessionStorage.getItem(key)
sessionStorage与localStorage的区别:
- sessionStorage在浏览器关闭(会话结束)时被清理;
- localStorage只要用户不主动清理就会一直存在。
通用事件监听
示例1:addEventListener
var btn = document.getElementById('btn1')
btn.addEventListener('click', function(event){
console.log('clicked')
})
示例2:
function bindEvent(elem, type, fn){
elem.addEventListener(type, fn)
}
var a = document.getElementById('link1')
bindEvent(a, 'click', function(e){
//此时获取的是超链接,点击默认行为是跳转
e.preventDefault()//阻止默认行为
alert(clicked)
})
注意:IE低版本使用attachEvent绑定事件,与W3C标准不一致;建议忽略。
事件冒泡
事件冒泡:节点优先处理绑定到自身的事件,再处理其父辈节点绑定的事件。
有以下html代码:要求点击激活弹出(alert)激活,点击取消弹出取消。
<body>
<div id="div1">
<p id="p1">激活</p>
<p id="p2">取消</p>
<p id="p3">取消</p>
<p id="p4">取消</p>
</div>
<div id="div2">
<p id="p5">取消</p>
<p id="p6">取消</p>
</div>
</body>
我们可以使用事件冒泡机制来处理:
var p1 = document.getElementById('p1')
bindEvent(p1, 'click', function(e){
e.stopPropagation()//阻止冒泡
alert("激活")
})
var body = document.body
bindEvent(body, 'click', function(e){
alert('取消')
})
e.stopPropagation()
很关键:事件冒泡机制中,节点优先处理绑定到自身的事件,再处理其父辈节点绑定的事件。如果不阻止事件冒泡,那么在点击激活
后,将先弹出激活
(绑定到自身的事件),再弹出取消
(绑定到父辈节点的事件)。
代理
如有以下html代码:
<div id="div1">
<a href="#">a1</a>
<img src="default.png">
<a href="#">a1</a>
<img src="default.png">
<!--随时新增更多a标签和img标签-->
</div>
使用事件委托(代理)的方式,来处理”点击A标签“事件。
var div1 = document.getElementById('div1')
bindEvent(div1, 'click', function(e){
e.preventDefault()
var target = e.target
if(target.nodeName === 'A'){
console.log('clicked')
}
})
页面加载
css/js/img页面加载说明
加载标签 | 说明 |
---|---|
css | 生成CSSOM,不阻塞DOM解析,但阻塞DOM渲染。 |
js | 同步加载,阻塞 |
img | 异步加载,不阻塞 |
Q:如何判断页面是否加载完毕?
A:使用window.addEventListener
或document.addEventListener
。
window.addEventListener
window.addEventListener('load', function(){
//页面的全部资源加载完才会执行,包括图片、视频等
})
document.addEventListener
document.addEventListener('DOMContentLoaded', function(){
//DOM渲染完即可执行,此时图片、视频可能还没加载完
})