变量提升
1、在JS中,函数和变量的声明都会被提升至函数的顶部,全局声明的会在解析时提升至顶部。
2、在JS中,变量可以先使用在声明。
3、在JS中,初始化变量不会造成变量提升。
实例1:
var fun = "我是字符串"
function fun () {
console.log("我是一个函数")
}
console.log(fun) // 我是字符串
console.log(fun()) // fun is not a function
该实例正常理解应该是字符串fun变量会被fun函数声明覆盖,但是为什么和我们想的不一样呢?这就是变量提升造成的影响了。 在我们写下JS代码后,会由JS解释器运行我们写的代码,在这个解释的过程中,解释器会把变量声明、函数声明提升至函数顶部(变量初始化不会提升,划重点,要考。),而在变量提升的过程中,函数声明比变量声明权重高,最终代码如下:
function fun () {
console.log("我是一个函数")
}
var fun = "我是字符串"
console.log(fun) // 我是字符串
console.log(fun()) // fun is not a function
是不是很无语,所以保持良好的编码习惯是多么重要,函数声明要写在变量声明前面,否则可能出现你意想不到的bug,即使不出现bug,解释器还是会帮你提升至顶部声明,救救解释器吧,它还只是个孩子。
实例2:
x = 5
console.log(x) // 5
var x
console.log(x) // 5
从该实例可以看出,变量是可以先使用在声明的,在声明变量未使用var关键字时,会隐式声明全局变量,该实例实际运行代码:
var x // 变量提升
x = 5;
console.log(x) // 5
console.log(x) // 5
再看个例子:
x = 5
console.log(x) // 5
var x = 3
console.log(x) // 3
不是说好变量要提升的吗?不应该都是输出5的吗?这里就是之前说的,初始化变量不会造成变量提升(var x = 3)。所以我们在编码时如果想避免变量的提升有两种办法:
1、提前在函数顶部声明变量。
2、声明变量时给一个初始值。
菜鸡前端在此提醒您:
规范千万条,遵守第一条。
代码不规范,同事两行泪。
对象的引用
俗话说:万物皆对象。依稀还记得有人问我这样的代码有什么问题:
var oldObj = {
name: "菜鸡前端"
}
var newObj = oldObj
newObj.name = "大佬前端"
console.log(oldObj.name)
console.log(newObj.name)
这是为啥啊,为啥俩都成了大佬前端?我只改了newObj的name属性啊。这就是对象的引用造成的,到底什么是对象引用呢?
当我们声明一个变量、函数、对象时,都会在内存中划分一块空间来作为存储空间,而这个变量就是这块内存空间的地址,举个栗子:
把内存空间看成一个篮子,oldObj变量就是这个篮子的使用方法(句柄),oldObj用这个方法放了一个菜鸡前端到篮子里。然后有另外一个人newObj也想用篮子,所以他就找oldObj要了这个方法var newObj = oldObj(这里只是要了使用篮子的方法,而不是把篮子拿过来了,重点,要考),然后newObj也可以往里面去放东西了,很开心,于是放了个叫大佬前端的进去。这个时候oldObj准备去把之前放进去的菜鸡前端拿出来,结果掏出个大佬前端,一脸懵逼。啥玩意啊,咋回事啊,还带自动升级的?其实这里就是由于俩人公用一个蓝子,都往里面放前端(实际是name属性),后面的会覆盖之前的,导致另一个去拿时,已经不是自己放的了,这里正确的做法应该是复制一个一模一样的篮子出来,而不是只拿到oldObj篮子(内存空间)的使用方法。
原型对象(prototype)
1、每个对象都有原型对象。
2、可以从中继承属性。
3、原型链在更新时不起作用。
4、当某个对象做出改变(不是原型对象做出改变),不会修改对象的原型。
实例:
var obj = function () {}
obj.prototype.a = 1
obj.prototype.b = 2
obj.prototype.fun = function () {
console.log("我是obj对象的原型方法fun")
}
var myObj = new obj() // new关键字,使用后返回一个对象的新实例,如不加new关键字,则返回函数的返回值,没有返回值返回undefined
var myObj2 = new obj()
console.log(obj.prototype.a) // 1
console.log(myObj.a) // 1 继承原型对象属性
myObj.a = 11 // 更新实例对象的a属性
obj.prototype.a = 2 // 更改原型
console.log(obj.prototype.a) // 2
console.log(myObj.a) // 11 实例对象做出改变,不影响原型对象,并且原型对象更新后,不会作用到myObj属性。
console.log(myObj2.a) // 2 这里由于myObj2是直接继承的,所以更新。
闭包的理解与运用
1、闭包可以封闭作用域。
2、闭包使用不当容易造成内存泄露。
实例:
function fun () {
var a = 0
}
fun()
console.log(a)
运行代码报错,这个很好理解吧?现在我又想在外部访问这个变量a,又不想把变量a写成全局变量,这可咋整,作为菜鸡前端的我,很是感到头大啊,想想大佬们都是怎么解决的,重新看个栗子:
function fun () {
var a = 0
return a
}
fun() // 0
恩,现在可以拿到变量a了,但是我还是感到不满足,为啥呢?这个方法啥也没干,我还有业务需要处理,还有好多逻辑要加进去,而且当需要访问的变量多了可咋整,还要传好多参数进去呢,这可咋办?在想想...看栗子:
function fun (num) {
var a = 0
var b = "嗨~"
return function (num) {
console.log(num + a)
}(num)
}
fun(1) // 1
这样确实可以传参和进行逻辑处理了,但是这个写法好傻,而且需要处理的方法多了可咋整,在想想...
function fun () {
var a = 0
var b = "嗨~"
return {
sumNum: function (num) {
return a + num
},
getStr: function (name) {
return b + ' ' + name
}
}
}
var obj = fun()
console.log(obj.getStr("菜鸡前端"), obj.sumNum(5)) // 嗨~ 菜鸡前端 5
舒服了~以上就是闭包啦~好处就是没有声明全局变量的情况下,使其也可以被外部访问,解决了多人合作开发时可能发生变量名冲突的问题。
链式调用
上面已经讲过,一个函数在没有加new关键字时,会返回函数中的返回值也就是return,没有return则返回undefined,利用好这个特性,我们就可以实现链式调用。相信很多同学看过这种写法:obj.add().get(),看上去是不是很高大上?我们来看看具体是怎么实现的:
重点:在对象内,返回对象本身。
实例:
function fun () {
var a = 0
return {
getA: function () {
return a
},
addA: function () {
a++
return this
}
}
}
fun().addA().getA() // 1
看,就是这么简单,很多时候其实并没有我们想象中那么难。
this指向
this的指向问题,这个相信大家在比较早期的阶段都遇到过...下面通过一个例子来说明一下:
var name = "我是全局name"
var o = {
name: "我是o对象的name",
getNameOne: function () {
console.log(this.name)
},
getNameTwo: function () {
var b = this.b.getName
b()
},
getNameThree: function () {
return this.b.getName()
},
b: {
name: "我是b对象的name",
getName: function () {
console.log(this.name)
}
}
}
o.getNameOne() // 我是o对象的name
o.b.getName() // 我是b对象的name
o.getNameTwo() // 我是全局的name
o.getNameThree() // 我是b对象的name
var t = o.b.getName
t() // 我是全局的name
什么鬼?这啥情况,这和我想的完全不一样啊!!!!
稳住,我们能赢!下面我们来慢慢分析:
1、this指向在函数声明时,是无法决定的,只有在被调用时才能确定this指向谁!(重点,要考)
2、当在当前对象查找不到时,会通过prototype对象向上逐级查找,直到window,window对象也没有返回undefined或报错(重点中的重点,必考)
记住上面这句话,然后往下看:
1、o.getNameOne()调用对象是o对象,o对象的getNameOne()中的this.name指向o对象,输出o.name。
2、o.b.getName()调用对象是b对象,b对象的getName()中this.name指向b对象,输出b.name>
3、o.getNameTwo()调用对象是o对象,o对象的getNameTwo()中 var b = this.b.getName; b()真实调用对象是局部变量b,b没有name属性,通过prototype对象逐级查找,查找到全局对象window,返回window.name(全局变量mame)。
4、o.getNameThree()调用对象时o,o对象的getNameThree()中this.b.getName()这里的this指向o,实际调用句柄为o.b.getName(),getName()调用对象为b,this指向b,输出b.name。
5、全局变量t调用o.b.getName,调用对象为全局变量t,b.getName()中this指向全局变量t,t没有name属性,向上查找到window对象,输出全局变量name。
看起来有点绕,但是不慌,多看看要考的重点,在尝试去理解,你会发现,其实就那么回事儿~
隐式转换和强制转换
1、string + number,number隐式抓换成string。
2、number - string,string转成number,string不是纯数字就会转成NaN。
3、* 、/、>、<、和第二条一样。
实例:
var str1 = "5"
var str2 = 1
console.log(str1 + str2) // 51
console.log(str1 - str2) // 4
console.log(str1 * str2) // 5
+符号不仅代表运算符,同时也是连字符,所以在和string类型一起运算时会变成连字符,无法正常运算,由于JS是弱类型语音,很容易出现因为类型错误而计算结果错误的情况。
菜鸡前端在此提醒您:
规范千万条,遵守第一条。
代码不规范,同事两行泪。