1.JS中常见的一些HTML事件?
(1)onclick:用户点击了HTML元素
(2)onchange:HTML元素已被改变
(3)onmouseover:用户把鼠标移到HTML元素上
(4)onmouseout:用户把鼠标移出HTML元素
(5)onkeydown:用户按下键盘按键
(6)onload:浏览器已经完成页面加载
2. HTML5新增的标签?
(1)视频标签:video
(2)音频标签:audio
(3)画布标签:canvas
canvas 元素使用 JavaScript 在网页上绘制图像,用来定义图形,画布是一个矩形区域。
(4)datalist:定义下拉列表
(5)header:定义section或page的页眉
(6)footer:定义 section 或 page 的页脚
(7)keygen:定义生成密钥
(8)nav:定义导航链接
(9)section:定义section
(10)article:定义文章
(11)aside:定义页面内容之外的内容
3.JS是异步还是同步语言?异步和同步有什么区别?
js是单线程的,只能处理完一件事再做另外一件事。
js的执行机制
(1)先判断是js代码是同步的还是异步的,同步的就是同步任务,直接进入主线程处理,异步的就进入任务列表
(2)当任务列表内的异步处理达到了触发条件时候(点击事件被点击时),就进入任务队列
(3)当所有的主线程的任务执行完毕之后,才会将任务队列里面的任务(回调函数)添加到主线程。
同步
按照一定的顺序去执行,执行完一个任务才能执行下一个
异步(ajax的经典例子)
一个任务正在做,它的下一个任务不必等前任务结束就可以继续执行(类似多线程)
4.a++和++a的区别?
a++是使用了a后,再对a进行加1。++a是先把a加1,然后再使用a
5.JS中基本数据类型和引用数据类型的区别?
(1)基本数据类型和引用数据类型
基本数据类型:Number、String 、Boolean、Null、Undefined和symbol
引用数据类型:Object
基本数据类型指的是简单的数据段,引用数据类型指的是有多个值构成的对象。
当我们把变量赋值给一个变量时,解析器首先要确认的就是这个值是基本类型值还是引用类型值。
(2)基本数据类型
基本数据类型是按值访问的,因为可以直接操作保存在变量中的实际值。示例:
var a = 10;
var b = a;
b = 20;
console.log(a); // 10值
上面,b获取的是a值得一份拷贝,虽然,两个变量的值相等,但是两个变量保存了两个不同的基本数据类型值。
b只是保存了a复制的一个副本。所以,b的改变,对a没有影响。
(3)引用数据类型
也就是对象类型Object type,比如:Object 、Array 、Function 、Data等。
与其他语言的不同是,你不可以直接访问堆内存空间中的位置和操作堆内存空间。只能操作对象在栈内存中的引用地址。
所以,引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存中堆内存中的对象。
var obj1 = new Object();
var obj2 = obj1;
obj2.name = "我有名字了";
console.log(obj1.name); // 我有名字了
说明这两个引用数据类型指向了同一个堆内存对象。obj1赋值给obj2,实际上这个堆内存对象在栈内存的引用地址复制了一份给了obj2,但是实际上他们共同指向了同一个堆内存对象。实际上改变的是堆内存对象。
(4)总结区别
a 声明变量时不同的内存分配:
1)原始值:存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。
这是因为这些原始类型占据的空间是固定的,所以可将他们存储在较小的内存区域 – 栈中。这样存储便于迅速查寻变量的值。
2)引用值:存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存地址。
这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。
地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。
b 不同的内存分配机制也带来了不同的访问机制
1)在javascript中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,
首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的按引用访问。
2)而原始类型的值则是可以直接访问到的。
c 复制变量时的不同
1)原始值:在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value而已。
2)引用值:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,
也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的改变都会反映在另一个身上。
(这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象指针的变量罢了)。多了一个指针
d 参数传递的不同(把实参复制给形参的过程)
首先我们应该明确一点:ECMAScript中所有函数的参数都是按值来传递的。
但是为什么涉及到原始类型与引用类型的值时仍然有区别呢?还不就是因为内存分配时的差别。
1)原始值:只是把变量里的值传递给参数,之后参数和这个变量互不影响。
2)引用值:对象变量它里面的值是这个对象在堆内存中的内存地址,这一点你要时刻铭记在心!
因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因了,因为它们都指向同一个对象。
6.JS中深拷贝与浅拷贝的区别和实现?
(1)深拷贝和浅拷贝的区别
浅拷贝:将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用;
深拷贝:创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”
//浅拷贝
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = obj1;
obj2.b = 40;
console.log(obj1);// { a: 10, b: 40, c: 30 }
console.log(obj2);// { a: 10, b: 40, c: 30 }
//深拷贝
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 40;
console.log(obj1);// { a: 10, b: 20, c: 30 }
console.log(obj2);// { a: 10, b: 40, c: 30 }
(2)浅拷贝的实现
a. object.assign()
let foo = {
a: 1,
b: 2,
c: {
d: 1,
}
}
let bar = {};
Object.assign(bar, foo);
foo.a++;
foo.a === 2 //true
bar.a === 1 //true
foo.c.d++;
foo.c.d === 2 //true
bar.c.d === 1 //false
bar.c.d === 2 //true
Object.assign()是一种可以对非嵌套对象进行深拷贝的方法,如果对象中出现嵌套情况,那么其对被嵌套对象的行为就成了普通的浅拷贝。
b.通过展开运算符 ...
来实现浅拷贝
//浅拷贝
let obj1 = { name: '张三', action: { say: 'hi'};
let obj2 = {... obj1};
obj2.name = '李四';
obj2.action.say = 'hello'
console.log('obj1',obj1)
// obj1 { name: '张三', action: { say: 'hello'}
console.log('obj2',obj2)
// obj2 { name: '李四', action: { say: 'hello'}
(3)深拷贝的实现
a. jQuery.extend()
$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝
//深拷贝
let obj1 = { name: '张三', action: { say: 'hi'};
let obj2 = $.extend(true, {}, obj1);
obj2.name = '李四';
obj2.action.say = 'hello'
console.log('obj1',obj1)
// obj1 { name: '张三', action: { say: 'hi'}
console.log('obj2',obj2)
// obj2 { name: '李四', action: { say: 'hello'}
b. 转成JSON
用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。
var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1); // { body: { a: 10 } }
console.log(obj2); // { body: { a: 20 } }
console.log(obj1 === obj2); // false
console.log(obj1.body === obj2.body); // false
缺点(局限性):
- 会忽略
undefined
- 会忽略
symbol
- 不能序列化函数
- 不能解决循环引用的对象
c.递归
var json1={"name":"shauna","age":18,"arr1":[1,2,3,4,5],"string":'got7',"arr2": [1,2,3,4,5],"arr3":[{"name1":"shauna"},{"job":"web"}]};
var json2={};
function copy(obj1,obj2){
var obj2=obj2||{};
for(var name in obj1){
if(typeof obj1[name] === "object"){
obj2[name]= (obj1[name].constructor===Array)?[]:{};
copy(obj1[name],obj2[name]);
}else{
obj2[name]=obj1[name];
}
}
return obj2;
}
json2=copy(json1,json2)
json1.arr1.push(6);
alert(json1.arr1); //123456
alert(json2.arr1); //12345
7.基本数据类型、引用数据类型、原始数据类型?
(1)基本数据类型:
null(空)
undefine(未定义)
boolean(布尔)
number(数字)
string(字符串)
symbol(独一无二的值)
(2)原始数据类型:
null
undefine
boolean
number
string
(3)引用数据类型:
object(对象)
Array(数组)
function(函数)
基本数据类型存储在栈内存(stack)中,值不会随其他变量的值改变而改变
引用数据类型是保存在堆内存(heap)中的对象,栈内存保存变量的指针,堆内存中保存具体的对象
8.animation的属性及属性值?
(1)@keyframes:规定动画
(2)animation-name:动画的名称
(3)animation-duration:规定动画完成一个周期所花费的秒或毫秒。默认是 0。
(4)animation-timing-function:规定动画的速度曲线。默认是 “ease”。
a. linear:匀速
b. ease:慢—快---慢
c. ease-in:以低速开始
d. ease-out:以低速结束
e. ease-in-out:快—慢---快
f. cubic-bezier(n,n,n,n):在 cubic-bezier 函数中自己的值。可能的值是从 0 到 1 的数值。
(5)animation-delay:延时执行。默认为0。
(6)animation-iteration-count: 规定动画被播放的次数。默认是 1。
a. n:播放n次
b. infinite:播放无限次
(7)animation-direction:是否逆向播放,默认normal
a. normal:不逆向播放
b. alternate:逆向播放
(8)animation-fill-mode:规定对象动画时间之外的状态。
a. none: 不改变默认行为。
b. forwards:停在动画最后一帧
c. backwards:停在动画最开始一帧
d. both:停在动画最开始和最后一帧
(9)animation-play-state:规定动画是否正在运行或暂停。默认是 “running”。
a. paused:规定动画已暂停
b. running:规定动画正在播放
9.事件冒泡、事件捕获、事件委托?
不论事件冒泡还是捕获都是在说事件发生的顺序
(1)事件冒泡:结构上(非视觉上)嵌套关系的元素,会存在事件冒泡的功能,即同一事件,自子元素冒泡向父元素。(子元素功能先执行,父元素再执行)
(2)事件捕获:结构上(非视觉上)嵌套关系的元素,会存在事件捕获的功能,即同一事件,自父元素捕获至子元素。(父元素的功能先执行,子元素再执行)
IE没有捕获事件
触发顺序:先捕获,后冒泡
不冒泡的事件:
a)UI事件:
- load事件(页面加载完执行)
- unload事件(用户离开页面执行)
- scroll事件(元素中滚动条滚动时执行)
- resize事件(浏览器窗口发生变化时执行)
b)焦点事件:
- blur事件(元素失去焦点时执行)
- focus事件(元素获得焦点时执行)
c)鼠标事件:
- mouseleave事件(鼠标离开元素时执行)
- mouseenter事件(鼠标进入元素时执行)
d)其他事件:
- change事件(元素的值发生改变时执行)
- submit事件(提交表单时执行)
- reset事件(重置表单时执行)
- select事件( textarea 或文本类型的 input 元素中的文本被选中时执行)
(3)默认事件:
是针对浏览器而言的,是浏览器的默认行为。
例如:
点击一个链接——跳转;
在表单中点击submit按钮——向服务器发送数据;
在文本上按住鼠标左键并移动——选中文本;
(4)阻止事件冒泡的方法:
A. 如果是通过addEventListener添加的事件:
1)event.stopPropagation();
2)return false;
B. 如果是在标签中用on来绑定事件:
<span οnclick="event.stopPropagation();
alert('you clicked inside the header');"
window.event.cancelBubble = "true">
</span>
(5)阻止默认事件的方法
1)event.preventDefault();
2)return false;
如果是在标签中用on来绑定事件,那么只能用 return false;
例如:
<input value="Focus works" onfocus="this.value=''">
<input onmousedown="return false" onfocus="this.value=''" value="Click me">
(6)事件委托:
利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
比如我们有100个li,每个li都有相同的click点击事件,为了减少与dom的交互次数,提高性能,只能用事件委托,可以只对它的父级(如果只有一个父级)这一个对象进行操作,这样我们就需要一个内存空间就够了。
A. 事件委托的原理
事件委托是利用事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开始,然后逐步向上传播事件, 举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。
B. 事件委托怎么实现
1)子元素实现相同的功能:
<ul id="ul1">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
实现功能是点击li,弹出123(遍历方法):
window.onload = function(){
var oUl = document.getElementById("ul1");
var aLi = oUl.getElementsByTagName('li');
for(var i=0;i<aLi.length;i++){
aLi[i].onclick = function(){
alert(123);
}
}
}
这样显然是不行的,因为点击ul也会弹出123,如果我想让事件代理的效果跟直接给节点的事件效果一样怎么办,比如说只有点击li才会触发,这时
Event对象提供了一个属性叫target,可以返回事件的目标节点,我们称为事件源,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom,当然,这个是有兼容性的,标准浏览器用ev.target,IE浏览器用event.srcElement,此时只是获取了当前节点的位置,并不知道是什么节点名称,这里我们用nodeName来获取具体是什么标签名,这个返回的是一个大写的,我们需要转成小写再做比较(习惯问题):
window.onload = function(){
var oUl = document.getElementById("ul1");
oUl.onclick = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li'){
alert(123);
alert(target.innerHTML);
}
}
}
这是li被点击的功能效果相同的例子,如果要是不同:
2)子元素实现不同的功能:
<div id="box">
<input type="button" id="add" value="添加" />
<input type="button" id="remove" value="删除" />
<input type="button" id="move" value="移动" />
<input type="button" id="select" value="选择" />
</div>
window.onload = function(){
var oBox = document.getElementById("box");
oBox.onclick = function (ev) {
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLocaleLowerCase() == 'input'){
switch(target.id){
case 'add' :
alert('添加');
break;
case 'remove' :
alert('删除');
break;
case 'move' :
alert('移动');
break;
case 'select' :
alert('选择');
break;
}
}
}
}
3)新增子节点
<input type="button" name="" id="btn" value="添加" />
<ul id="ul1">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
现在是移入li,li变红,移出li,li变白,这么一个效果,然后点击按钮,可以向ul中添加一个li子节点
window.onload = function(){
var oBtn = document.getElementById("btn");
var oUl = document.getElementById("ul1");
var aLi = oUl.getElementsByTagName('li');
var num = 4;
//鼠标移入变红,移出变白
for(var i=0; i<aLi.length;i++){
aLi[i].onmouseover = function(){
this.style.background = 'red';
};
aLi[i].onmouseout = function(){
this.style.background = '#fff';
}
}
//添加新节点
oBtn.onclick = function(){
num++;
var oLi = document.createElement('li');
oLi.innerHTML = 111*num;
oUl.appendChild(oLi);
};
}
这是一般的做法,但是你会发现,新增的li是没有事件的,说明添加子节点的时候,事件没有一起添加进去,这不是我们想要的结果,那怎么做呢?一般的解决方案会是这样,将for循环用一个函数包起来,命名为mHover,如下:
window.onload = function(){
var oBtn = document.getElementById("btn");
var oUl = document.getElementById("ul1");
var aLi = oUl.getElementsByTagName('li');
var num = 4;
function mHover () {
//鼠标移入变红,移出变白
for(var i=0; i<aLi.length;i++){
aLi[i].onmouseover = function(){
this.style.background = 'red';
};
aLi[i].onmouseout = function(){
this.style.background = '#fff';
}
}
}
mHover ();
//添加新节点
oBtn.onclick = function(){
num++;
var oLi = document.createElement('li');
oLi.innerHTML = 111*num;
oUl.appendChild(oLi);
mHover ();
};
}
虽然功能实现了,看着还挺好,但实际上无疑是又增加了一个dom操作,在优化性能方面是不可取的,那么有事件委托的方式,能做到优化吗?
window.onload = function(){
var oBtn = document.getElementById("btn");
var oUl = document.getElementById("ul1");
var aLi = oUl.getElementsByTagName('li');
var num = 4;
//事件委托,添加的子元素也有事件
oUl.onmouseover = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li'){
target.style.background = "red";
}
};
oUl.onmouseout = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li'){
target.style.background = "#fff";
}
};
//添加新节点
oBtn.onclick = function(){
num++;
var oLi = document.createElement('li');
oLi.innerHTML = 111*num;
oUl.appendChild(oLi);
};
}
4)子元素还有不同的子元素
<ul id="test">
<li>
<p>11111111111</p>
</li>
<li>
<div>
22222222
</div>
</li>
<li>
<span>3333333333</span>
</li>
<li>4444444</li>
</ul>
如上列表,有4个li,里面的内容各不相同,点击li,event对象肯定是当前点击的对象,怎么指定到li上:
var oUl = document.getElementById('test');
oUl.addEventListener('click',function(ev){
var target = ev.target;
while(target !== oUl ){
if(target.tagName.toLowerCase() == 'li'){
console.log('li click~');
break;
}
target = target.parentNode;
}
})
核心代码是while循环部分,实际上就是一个递归调用,你也可以写成一个函数,用递归的方法来调用,同时用到冒泡的原理,从里往外冒泡,直到currentTarget为止,当当前的target是li的时候,就可以执行对应的事件了,然后终止循环