目录
1.JS中的引用赋值与传值赋值
JS中字符串的值是无法被改变的,例如
var S1 = "hello world!"
S1[0] = 'a'
console.log(S1[0]) //打印'h',不会变成'a'
但实施以下操作是成立的:
var s1 = "heihei"
s1 += "haha"
console.log(s1) //"heiheihaha"
var s2 = "world"
s2 = "hello"
console.log(s1) //"hello"
但是,这里的s1最后 已经指向了一个新的地址空间,而不是在原来的地址空间上对字符串做修改。原来的地址空间存放的字符串还是"heihei"。同理,s2也是指向了一个新的地址空间
在JS中,对于基本类型:string、number、boolean、null、undefined,他们的变量是存放在栈区的,也就是说可以操作保存在变量中实际的值。当基本类型赋值的时候,赋的是实际的值,例如
var a = 10
var b = a
a和b是没有关联的,b由a复制得到,他们在内存中的关系如下:
a和b是两个不同的空间。
对于引用类型,Array、Function、Object。可以拥有属性和方法,并且我们可以修改其属性和方法。引用对象存放的方式是:在栈中存放对象变量标示名称和该对象在堆中的存放地址,在堆中存放数据。对象使用的是引用赋值。当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在堆中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。举个栗子:
var a1 = [1,2,3];
var b1 = a1;
a1 = [4,5,6];
alert(b1); //[1,2,3]
var a2 = [1,2,3];
var b2 = a2;
a2.pop();
alert(b2); //[1,2]
a1 = [4,5,6];//改变的是a引用本身,没有改变数组对象,a1和b1没有了关系。
a2.pop();//改变的是数组对象,a引用没有改变。
b2 = a2;//该操作后,b直接指向数组对象,不是b指向a,a再指向数组。
所以改变a引用并不会对b引用造成影响,改变数组对象可以。
2.JS运行机制
<ul>
<li>click me</li>
<li>click me</li>
<li>click me</li>
<li>click me</li>
</ul>
var elements=document.getElementsByTagName('li');
var length=elements.length;
for(var i=0;i<length;i++){
elements[i].onclick=function(){
alert(i);
}
}
依次点击4个li标签,会依次弹出4,4,4,4
JS运行机制:事件(click,focus等等),定时器(setTimeout和setInterval),ajax,都是会触发异步,属于异步任务;js是单线程的,一个时间点只能做一件事,优先处理同步任务; 按照代码从上往下执行,遇到异步,就挂起,放到异步任务里,继续执行同步任务,只有同步任务执行完了,才去看看有没有异步任务,然后再按照顺序执行! 这里for循环是同步任务,onclick是异步任务,所以等for循环执行完了,i变成4了,注意:这里因为i是全局变量,最后一个i++,使得i为4(后面的onclick函数,最后在循环外面执行,不受i<length限制); 所以for循环每执行一次,onclick事件函数都会被挂起一次,共4次; for循环结束后,点击事件 触发了4个onclick函数,接着输出4个4!
3.JS中获取对象
在js里面添加的属性名使用驼峰法,在css里面使用连接线。
除了id和query 其他返回的都是节点列表。document.getElementClass返回的是一个数组
4.JS中的空对象转为布尔类型
5.onclick等事件加不加括号问题
第一种情况:
在上图中,这是一个onclick函数,clickIt()则是在函数内部,所以当触发了onclick事件的时候,onclick函数就执行,这时候因为clickIt在onclick函数内部已经执行了,所以会打印出来hahaha.
如果改成οnclick="clickIt",这时候打印出来的就是function onclick(){ },而不会打印出hahaha,所以即使点击了按钮,触发了onclick事件,clickIt依然不会执行。
第二种情况:
上图中,是用DOM0级绑定点击事件的方法,打印onclick,结果发现onclick就是clickIt函数。
所以这个时候clickIt后面就不用加括号,当你触发onclick事件的时候,直接就会执行.
如果要是在clickIt后面加个括号,不触发onclick事件也会直接执行clickIt。
总结:加上括号是执行的意思,添加事件的回调函数应该就是给相应的事件属性赋值,而很明显需要把一个函数赋值给这个事件属性,而不是函数的调用结果。所以在js中的绑定是直接赋值(上述第二种情况)。而在标签内的事件属性的值是由引号包裹的,代表的是当点击该元素时,执行引号内的代码,直接把引号内的代码拿出来跑,如果不加括号,那就不会调用那个函数(上述第一种情况)。
在Vue.js里,@click点击事件带不带括号没有多大区别,因为Vue会在底层把传过去的函数统一解析成方法,如果带小括号的话说明有相应的实参要传入方法体里面
6.html5的优势
1.有明确的语义,语义化(header,footer,aside……)代码可读性更高
2.对视频和音频的支持,可以插入
3.本地存储,localStorage和SessionStorage
4.移动端
7.ES6的特性
8.css3新特性
transform、transition、animation
9.css选择器
优先级:
1.越具体的优先级越高(#xxx > .yyy)
2.同一个层级的,写在后边的优先级高
<div class = "xxx yyy"></div>
.xxx{width:20px}
.yyy{width:50px}
3.属性后面加了!important的优先级最高
10.setTimeout和Promise的执行顺序
setTimeout(function () {
console.log('定时器开始啦')
});
new Promise(function (resolve) {
console.log('马上执行for循环啦');
for (var i = 0; i < 10000; i++) {
i == 99 && resolve();
}
}).then(function () {
console.log('执行then函数啦')
});
console.log('代码执行结束');
//输出:马上执行for循环啦 代码执行结束 执行then函数啦 定时器开始啦
11.JS里的对象类型
JS中,可以将对象分为“内部对象”、“宿主对象”和“自定义对象”三种。
1.内部对象
js中的内部对象包括Array、Boolean、Date、Function、Global、Math、Number、Object、RegExp、String以及各种错误类对象,包括Error、EvalError、RangeError、ReferenceError、SyntaxError和TypeError。
其中Global和Math这两个对象又被称为“内置对象”,这两个对象在脚本程序初始化时被创建,不必实例化这两个对象。
2.宿主对象
宿主对象就是执行JS脚本的环境提供的对象。对于嵌入到网页中的JS来说,其宿主对象就是浏览器提供的对象,所以又称为浏览器对象,如IE、Firefox等浏览器提供的对象。不同的浏览器提供的宿主对象可能不同,即使提供的对象相同,其实现方式也大相径庭!这会带来浏览器兼容问题,增加开发难度。
浏览器对象有很多,如Window和Documen,Element,form,image,等等。
3.自定义对象
顾名思义,就是开发人员自己定义的对象。JS允许使用自定义对象,使JS应用及功能得到扩充
12.JS里的变量回收规则
1.全局变量不会被回收。
2.局部变量会被回收,也就是函数一旦运行完以后,函数内部的东西都会被销毁。
3.只要被另外一个作用域所引用就不会被回收
13.Array对象方法哪些会改变原数组哪些不会
以下这些方法会改变原数组
var arr = []
arr.splice()返回被删除的项目
arr.reverse()
arr.fill()
arr.copyWithin()
arr.sort()
arr.push()返回的数组长度
arr.pop()
arr.unshift()
arr.shift()
不会改变原数组
var arr = []
arr.slice()
arr.map()
arr.forEach()
arr.every()
arr.some()
arr.filter()
arr.reduce()
arr.entries()
arr.find()
arr.concat('1',['2','3']) //[1,2,3]
Array 的reduce方法
实现 一个累加器,
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
如果没有指定initialValue,则把array中第一个元素作为初始值,total初始值是array第一个元素,currentValue从数组中第二个元素开始。
如果指定了initialValue,total初始值就是initialValue,currentValue从array中第一个元素开始。
14.let与setTimeout的问题
//(1)
for(var i = 0; i < 4; i ++){
let j = i
setTimeout(function(){
console.log(j)
}, 1000)
}//0,1,2,3,4
//(2)
for(let i = 0; i < 5; i ++){
setTimeout(function(){
console.log(i)
}, 1000)
}//0,1,2,3,4
//(3)
for(var i = 0; i < 5; i++){
(function(){
var j = i
setTimeout(function(){
console.log(j)
}, 1000)
})()
}//0,1,2,3,4
(1)中,在每一次循环的时候,都会有一个let j = i,let会创建一个块级作用域,五次循环的j是不同的地址。let j = i后遇到了setTimeout,这是一个WebAPI(如下图),将setTimeout的回调函数放进异步任务的的等待队列里:
这里的setTimeout的回调函数指的是
function(){ console.log(j) }
放进等待队列的时候,console.log(j)这句话里的j的地址也被指定了。由于5次j是不同的地址,所以会输出0,1,2,3,4。
当同步任务(这段代码里是for循环)执行完了后,js引擎就会去等待队列里读取异步任务(就是五次setTimeout 的回调函数),然后开始执行这些异步任务,输出0,1,2,3,4
(2)的原理与(1)相同。如果(2)的循环体改成
for(var i = 0; i < 5; i ++){}
var不会创建块级作用域,每一次循环的i都是同一个地址,所以在输出的时候就会输出5,5,5,5,5
(3)这段代码里有一个立即执行函数表达式,立即执行函数表达式也会创建函数作用域。每一次循环的时候,里面的匿名函数都会立即执行,然后var j = i,然后遇到setTimeout,把回调函数放进等待队列,j的引用也被放进去。值得注意的是,因为立即执行函数表达式也会创建函数作用域,这里就创建了5个不同的函数作用域。不同的函数作用域的变量存放地址不同,也就是说这里的j是5个不同地址的j,所以在输出的时候也会输出0,1,2,3,4
//(3)
for(var i = 0; i < 5; i++){
(function(){
var j = i
setTimeout(function(){
console.log(j)
}, 1000)
})()
}//0,1,2,3,4
设定一个 150ms 后执行的定时器不代表到了 150ms 代码就立刻执行,它表示代码会在 150ms 后被加入到队列中。如果在这个时间点上,队列中没有其他东西,那么这段代码就会被执行。
15. 变量与变量重名、变量与函数重名问题
(1)变量与变量重名
如果仅声明则忽略
var a = 1
var a //会忽略这个声明
console.log(a) //1
如果赋值了,则覆盖原值
var a = 1
var a = 2
console.log(a) //2
(2)变量与函数重名
变量与函数都存在变量提升的问题,例如以下代码
var a = 1
function a(){console.log('ha')}
这段代码在预解析的时候,会解析成这样:
function a() {}
var a
函数声明比变量声明提升到更高的位置,当处于预解析阶段时,内存分配过程应当是这样的:
预解析开始
第一行:为funcion a()分配空间,把function a的代码片放进去(注意此时a函数并没有执行,因为还没有被调用)
第二行:var a 声明a变量,此时a变量还没有类型,只是声明
变量a也是指向了同一块区域,此时变量a并没有赋值,所以没有类型。由于这块内存已经存放了一个函数类型的a,所以此时变量a的类型也应是函数。
预解析结束
此后,就会根据函数被调用的位置和变量被赋值的位置,决定a到底是个啥
(1)如果变量赋值在先:
var a
function a(){console.log('a')}
a = 1
a() //报错,a不是一个函数
在第三行,a被赋值为1,那么上述属于a的空间,就会存进1,并且类型是Number,之前的函数类型会被覆盖,所以现在a是个变量,而不是一个函数,所以如果之后再调用函数a就会报错。
(2)如果函数调用在先
var a
function a(){console.log('ha')}
a() //输出ha
a = 1
a()//报错,a不是一个函数
在代码运行到第三行时,a还是一个函数(变量a还没有被赋值),所以调用a可以成功,输出ha。第四行,a被赋值为1,此后a不再是一个函数了,而是一个变量,所以第五行执行a()会报错(a不是一个函数)