js进阶开发指南

最为一个项目驱动型的前端小白,相信有不少人与我一样草草学习js后就连开始了项目的编写,虽然最后功能实现了,但总觉得自己的js代码哪里有些怪怪的,今天在读完了《你不知道得javascript上卷》后,才发现岂止是怪,以前的代码只能用惨不忍睹来形容。

这本书确实是nb,讲述了很多平时经常用但又一知半解的问题,在讲述行为委托设计前,我会将这些知识点也一一列出,供自己和看到这篇文章的有缘人(如果有的话)在今后不再被这些问题所迷惑。

1.LHS和RHS

L和R分别是'left'和'right'的缩写,故LHS和RHS的中文分别翻译过来就是左引用和右引用。这里的左和右是相对于赋值等号而言的。左引用即是在进行赋值语句时,引擎查询变量的存放地址的行为,因为只有查询到变量的存放地址,js才能将声明的数据放入其中,若是没有找到该变量,则会在全局中创建该变量。RHS则是使用某个变量时,引擎查询变量所存储的值的行为,这时若未找到该变量,则会抛出“ReferenceError”的异常,而若引擎找到了该变量,但对结果的操作不合法(例如对非函数的值进行函数引用),则会抛出“TypeError"的异常。

2.避免使用eval() 和with

eval()和with会在运行时修改或创建新的词法作用域,但事实上,javascript会在编译阶段基于代码的词法分析进行优化,而这两个小倒霉蛋会破坏这一行为而降低代码的性能,所以必须摒弃(事实上这两个东西用起来也不怎么顺手,往往还会造成一定理解上的困难),总之,不用就对了!

3.IIFE(立即执行函数表达式)

一帮形如(function{})()的式子称为立即执行函数表达式。其存在的意义有利用函数作用域的优势封装变量名,防止全局变量的命名池被污染(但在ES6引入了块级作用域的概念后可以用let及const来命名变量解决这个问题),进阶的做法是在第二个括号中传入参数,也有在第二各括号中定义函数,并将该函数作为形参传入第一个括号中运行来达到倒置代码运行效果的作用。

4.闭包

闭包可谓是区分一名js开发人员水平的一个重要模块了,就个人而言,在不理解闭包之前,确实觉得这个词非常神秘,但理解了之后,却又觉得这个词很贴切,而且并不需要消耗什么脑细胞去特意理解它。

先说结论,所谓闭包就是将持有某一词法作用域的函数作为返回值传递到外面的作用域,使得外边的作用域也可以访问到里面的词法作用域的方法。

举一个典型的例子

function foo(){

var a=2;

function bar(){

console.log(a);

}

return bar;

}

var baz=foo();

baz();               //控制台会打印结果2

在上面的例子中,外部的baz函数的值是被封装在foo方法中的a的值,按理说a的值是无法被外面的函数所访问到的,但由于bar函数的返回值被返回了出来,实质上相当于一个接口,所以a的值最终被外部函数访问到了。

由此可以看出,闭包的作用就是在保证了内部的私密性的条件下,通过返回函数值提供与外面进行交流的接口,使得外部能与该词法作用域进行交互。

闭包的最大作用就是实现了js的模块化机制,就个人而言感觉js的模块化机制的变成思维与类相似,不过与“混入”式的模仿类的编程在js中代码显得更加整洁易理解且不容易出现难以理解的错误,总之算是一种很不错的代码格式。

5.this

讲真,我觉得很多人都觉得自己理解this,因为this不就是这个嘛,就是当前操作的对象嘛!当你写一些简单的代码时,你这样理解确实也不会有什么问题,但当你真正碰见的问题时,你就会发现this这个熟悉的关键字看起来又是辣么的陌生!

首先要明确一点,this确实是指向一个对象,但具体会指向哪个对象呢,规则如下

1.隐式绑定

结论:this指向函数的调用位置。

例子:

function foo(){

console.log(this.a)

}

var obj={

a:2

foo:foo

}

obj.foo //2

在这里我们就能明显看出this并不是根据词法作用域来绑定对象的,即跟代码的位置没有关系,实际上是根据函数对象的调用位置来进行绑定的。在这个例子中,foo函数是在obj对象上调用的,所以foo函数中的this关键字绑定到了obj对象上,因此正确输出了位于obj对象中的a的值

根据这个原理,一些平时难以理解的现象就得到了相当合理的解释,请看下面的例子

function foo(){

console.log(this.a);

}

var obj={

a:2

foo:foo

};

var bar=obj.foo;

var a="ooops,global";

bar() //"oops,global"

在这里,bar函数中的this绑定到了全局对象上(即window对象),问题出现在bar实际上是obj.foo函数的一个别名,当调用bar时,实际上是在全局对象上调用了该方法,这时的this就会绑定到全局对象上

其实这种解释同样适合于下面的这个例子

function foo(){

console.log(this.a);

}

var obj={

a:2

foo:foo

}

var a="oops,global";        //a是全局对象的属性

setTimeout(obj.foo,100);    //“oops,global”

上面的例子中obj.foo其实也是一种隐式赋值,即形参也相当于一个函数的别名,所以this会泄露到全局变量上

2.显示绑定

所谓的显示绑定就是使用函数的call(),apply(),bind()这几种方法,通过将需要绑定的对象通过第一个形参来进行显示this绑定的方法。其中前两者是接受要绑定对象的形参到函数中的this关键字上后执行该函数,而bind()则是绑定后返回该函数。

例如下面这行代码展示了使用call()进行this的绑定

function foo(){

console.log(this.a);

}

var obj={

a:2

}

foo.call(obj) //2

上面的例子中显式的将obj对象绑定在了foo函数中的this关键字上

3.new 绑定

使用new来调用函数,会自动执行下面操作

1.创建(构造)一个新的对象。

2.对新对象执行[Prototype]连接

3.将新对象绑定到函数的this关键字上

4.如果函数没有返回其它对象,则new表达式中的函数调用会自动返回这个对象

例如

function foo(a){

this.a=a;

}

var bar=new foo(2);

console.log(bar.a);     //2

在上面的例子中bar被赋值了新创建的对象,而新创建的对象则被绑定到了foo函数的this关键字上。

4.箭头函数的特殊this绑定规则

使用=>(胖箭头)创建的函数中的this关键词是根据词法作用域来决定绑定规则的,即会继承父级对象的this,且一旦绑定,则无法修改。

这一点与bind类似,可以用来解决类似于setTimeout等回调函数丢失bind绑定的问题

以上就是this的全部绑定规则了!可以看出this的规则是更加强调代码实际运行时的函数调用位置的,与一般的理解并不相同。

6.使用行为委托的设计风格编写js代码

js不同于传统的面向对象的语言,他没有类和实例的概念,即使是new也不能看做是典型的构造函数,因为并没有发生依照模板复制成一个新副本后赋值给新实例的过程,所以我们更应该用好js独有的prototype机制,使用行为委托的编码风格。

以下为推荐的代码形式:

Task={

setID:function(ID){this.id=ID},

outputID:function(){console.log(this.id);

}

XYZ=object.create(Task);

XYZ.prepareTask=function(ID,label){

this.setID(ID);

this.label=Label;

};

XYZ.outputTaskDetails=function(){

this.outputID();

console.log(this.label);

}

在上面的例子中,一切的一切都是对象!没有类!在这里,XYZ通过object.create(...)方法进行的创建,这使得它的prototype委托给了Task对象。我们注意到,setID和outputID并非是定义在XYZ内部的方法,但是通过原型链的查找,事实上XYZ调用了Task内部中定义的方法,而操作的对象则是由this传递了过去(在里面发生了两次隐式绑定)

另外需要补充说明的是在行为委托代码设计中最好把状态保存在委托者而非委托目标上,同时尽量少使用容易被重写的通用方法名,提倡更具有描述性的命名方式,尤要写清对象的行为类型

下边是一个稍加复杂但是更加实际的例子

Foo={

init:function(who){

this.me=who

}

identify:function(){

return "i am"+this.me;

}

};

Bar=Object.create(Foo);

Bar.speak=function(){

alert("hello"+this.identify()+".");

}

var b1=Object.create(Bar);

b1.init("b1");

var v2=Object.create(Bar)

b2.init("b2");

b1.speak();

b2.speak();

在上面的例子里Bar委托给了Foo,而b1和b2又委托给了Bar,形成了一条原型继承链,其中Foo中的方法是最通用的,因为它位于委托链的顶端,任何对象都可以通过委托的方式使用Foo对象中的方法,所以像init这样的初始化函数就定义在了Foo对象中,而Bar又新定义了speak方法,这使得委托对象b1,b2可以调用speak函数。

总之行为委托方法才是最切合js最初设计模式的方法,这种方法的代码往往直观而又整洁,是非常值得一试的代码风格。

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值