文章目录
- 立即执行函数IIFE
- 认识
- Jquery中的立即执行函数
- 立即执行函数的常见写法
- W3C推荐写法和常见写法
- 传递参数
- 返回值
- 立即执行函数执行完就被销毁
- 表达式的执行符号()和逗号运算符
- IIF中window和return
- IIF使用return返回内部方法
- IIF把内部方法暴露在window上
- IIF前面的分号问题
- 插件的写法
- 模仿块级作用域和for循环
- IIF模块化开发
- 私有变量
- 构造函数中定义特权方法
- 基于原型定义静态私有变量
- 模块模式
- 增强的模块模式
立即执行函数IIFE
认识
立即调用函数表达式
//在定义时就会立即执行的 JavaScript 函数。
(function () { //匿名函数自调用
console.log('...')
})();
- 这是一个被称为 自执行匿名函数 的设计模式,主要包含两部分。
- 第一部分是包围在 圆括号运算符 () 里的一个匿名函数,这个匿名函数拥有独立的词法作用域。
这不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域。- 第二部分再一次使用 () 创建了一个立即执行函数表达式,JavaScript 引擎到此将直接执行函数。
- 当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问。
(function () {
var name = "Barry";
})();
// 无法从外部访问变量 name
name // 抛出错误:"Uncaught ReferenceError: name is not defined"
- 将IIFE 分配给一个变量,不是存储 IIFE 本身,而是存储 IIFE 执行后返回的结果。
var result = (function () {
var name = "Barry";
return name;
})();
// IIFE 执行后返回的结果:
result; // "Barry"
Jquery中的立即执行函数
(function(){
var a =1
function test(){
console.log(++a)
}
window.$=function(){ //向外暴露一个全局函数
return test
}
})()
$()() //2
立即执行函数的常见写法
immediately invoked function
初始化函数
执行后就销毁无法调用
W3C推荐写法和常见写法
//两种写法
//1 常见
//在一个小括号里面写匿名函数
//在小括号外面加一个执行的小括号
(function(){
})()
//2. w3c建议
//在一个小括号里面写匿名函数
//在匿名函数后面加一个执行的小括号
(function(){
}())
传递参数
//也可以传递参数
(function(a,b){
console.log(a+b)
}(2,4))
返回值
//立即执行函数也有返回值
//把他交给一个变量
var num = (function(a,b){
return a+b
}(2,4))
console.log(num)
立即执行函数执行完就被销毁
立即执行函数的名称有没有都无所谓
//----------------------------------
(function test1(){
console.log(1)
})()
var test2 = function(){
console.log(2)
}()
console.log(test2)//undefined 立即执行函数执行完就被销毁
//所以立即执行函数的名称有没有都无所谓
表达式的执行符号()和逗号运算符
- 将函数变成表达式才能被执行符号()执行。
- 小括号里面的都是表达式
- 将函数声明变成表达式的方法 + - ! || && 这样加小括号可以直接执行
- 函数前面加分号不能使其变成表达式
小括号里都是表达式-面试题
// 将一个函数用括号包裹起来就是表达式,函数表达式是没有函数名字的
// 所以b是undefined
var a = 10;
if(function b(){}){
a+=typeof(b)
}
console.log(a) //10undefined
其他面试题
//test3语法错误 无法执行
function test3(){
console.log(3)
}()
//将函数变成表达式才能被执行符号()执行。
//小括号里面的都是表达式
// ================================
//将函数声明变成表达式的方法 + - ! || && 这样加小括号可以直接执行
0 || function test3(){
console.log(3)
}()
// ================================
//正常执行
function test3(){
console.log(3)
}(6) //(6)被js引擎单独作为表达式执行,所以不报错
// ================================
(6,7) //7 逗号运算符
//按顺序连接多个表达式,先从左到右计算所有运算数,然后返回最后一个运算数的值。
// ================================
//面试题3 逗号运算符和立即执行函数
var fn =(
function test1(){
return 1
},
function test2(){
return 2
}
)();
//fn = 2
console.log(typeof fn) //number
// ================================
//面试题4
var a=10
if(function b(){}){
a+=typeof(b)
}
console.log(a) //10undefined
//b为啥是undefined
//(function b(){})执行后b为undefined。
//小括号里面是表达式,会忽略掉函数的名字
(function b(){console.log('b')}) //这句返回undefined
(var a = 3) //错误 ,括号中不能写赋值语句,只能写表达式
IIF中window和return
IIF使用return返回内部方法
//1.1 使用return 闭包对函数内部的变量每次加1
function test(){
var a =1
function plus(){
a++
console.log(a)
}
return plus
}
var plus = test()//全局函数
plus()//2
plus()//3
plus()//4
//2.1 使用一个变量接收立即执行函数 闭包对函数内部的变量每次加1
var add = (function(){
var a=1
function add(){
a++
console.log(a)
}
return add
})();
add()//2
add()//3
add()//4
IIF把内部方法暴露在window上
//1.2 使用window 闭包对函数内部的变量每次加1
function test(){
var a =1
function add(){
a++
console.log(a)
}
window.add=add
}
test()
add()//2
add()//3
add()//4
//2.2 使用立即执行函数把内部函数暴露在window上
(function(){
var a=1
function add(){
a++
console.log(a)
}
window.add = add
})();
add()//2
add()//3
add()//4
IIF前面的分号问题
//
(function(){
function Test(){
}
window.Test = Test
})()
var test = new Test()
//立即执行函数前面的分号(实际上就是语义问题)->js中该不该写分号的问题
//(function(){})()
//(function(){})()// 报错
;(function(){})()
;(function(){})()
插件的写法
- 立即执行函数防止变量污染
- ES5没有块作用域
- 原型链的顶端是object.prototype
- 函数不写返回值默认return undefined
- 构造函数实例化后默认返回this对象
- 插件通用写法
;(function(){
var Test = function(){
}
Test.prototype = {
}
window.Test = Test;
})()
- 计算器插件
//计算器插件
;(function(){
var Compute = function(opt){
this.x=opt.firstNum||0;
this.y=opt.secondNum||0;
}
Compute.prototype = {
plus:function(){
return this.x+this.y
},
minus:function(){
return this.x-this.y
},
mul:function(){
return this.x*this.y
},
div:function(){
return this.x/this.y
},
//也可以直接只写方法
div2:function(xx,yy){
return xx/yy
}
}
window.Compute = Compute
})()
var compute = new Compute({
firstNum:1,
secondNum:2
})
compute.div()
compute .div2(3,4)//0.75
- 打印字符串字节数的插件
ASCII码
- ASCII码 都是一个字节
- 表一 0-127
- 表二 128-255
- UNICODE码 涵盖 ASCII码
- 所以他得前255是ASCII码,一个字节
- 256之后就是两个字节
var str ='a'
console.log(str.charCodeAt(0))
//
var getBytes = function(str){
var bytes = 0
for(var i =0;i<str.length;i++){
var pos = str.charCodeAt(i)
if(pos <=255){
bytes++ //前255占一个字节
}else{
bytes+=2//后面占两个字节
}
}
return bytes
}
console.log(getBytes('你好,世界!HELLO World!'))//22
var getBytes = function(str){
var bytes = str.length
for(var i =0;i<str.length;i++){
var pos = str.charCodeAt(i)
if(pos > 255){
bytes++ //前255占一个字节
}
}
return bytes
}
console.log(getBytes('你好,世界!HELLO World!'))//22
模仿块级作用域和for循环
ES 中没有块级作用域的概念
这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的
function outputNumbers(count){
for(var i=0;i<count;i++){
console.log(i)
}
alert(i)//计数
}
在这个函数中定义了一个for循环
而变量i的初始值被设置为0.
在java,c++等语言中,变量i只会在for循环的语句中定义,循环一旦结束,变量i就会被销毁。
可是在ES中,变量i是定义在当前函数的活动对象AO中。
因此从他有定义开始,就可以在函数内部随处访问它。
JS从来不会告诉你是否多次声明了同一个变量:
遇到这种情况,它只会对后续的声明视而不见。
不过它会执行后续声明中的变量初始化。
匿名函数可以用来模仿块级作用域并避免这个问题
(function(){
//这里是块级作用域
})()
定义并立即调用了一个匿名函数。
将函数声明包含在一对圆括号中,表示它实际是一个函数表达式。
而紧随其后的另一对圆括号会立即调用这个函数
var someFunction = function(){
//这里是块级作用域
}
someFunction()
这个例子先定义了一个函数,然后立即调用了它。
定义函数的方式是创建了一个匿名函数,
并把匿名函数赋值给变量someFunction
function(){
//这里是块级作用域
}() //出错
这段代码会导致语法错误。
因为js将function关键字当做一个函数声明的开始,而函数声明后面不能跟园括号。
然而函数表达式后面可以跟圆括号。
要将函数声明转换成函数表达式式,可以给他加上圆括号
(function(){
//这里是块级作用域
})()
无论在什么地方,只要临时需要一些变量,就可以使用块级作用域
function outputNumbers(count){
(function(){
for(var i=0;i<count;i++){
console.log(i)
}
})()
alert(i)//计数->报错
}
这个重写后的outputNumbers函数中
我们在for循环外部插入了一个私有作用域。
在匿名函数中定义的任何变量,都会在函数结束后被销毁
因此变量i只能在循环中使用,使用后被销毁
而在私有作用域中能够访问count,是因为这个匿名函数是一个闭包,他能够访问包含作用域中的所有变量。
在一个由很多开发人员参与的大型程序中,过多的全局变量和函数很容易导致命名冲突。
而通过创建私有作用域,每个开发人员都可以使用自己的变量,又不必担心全局作用域
(function(){
var now = new Date()
if(now.getMonth()==0 && now.getDate()==1){
alert('Happy new year!')
}
})()
上述代码放在全局作用域中,可以确定哪一天是1月1日。
其中变量now是在匿名函数中的局部变量,我们不必在全局作用域中创建它
这种做法可以减少闭包占用的内存问题,因为没有执行匿名函数的引用。
只要函数执行完毕,就可以立即销毁其作用域链
IIF模块化开发
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script>
// 不让立即执行
window.onload = function(){
init();
}
function init(){
console.log(initFb(10));
console.log(initDiv(100));
}
var initFb = (function(){
function fb(n){
if(n<=0){
return 0
}
if(n<=2){
return 1
}
return fb(n-1) + fb(n-2)
}
return fb;
})()
var initDiv = (function(){
function div(n){
var arr = [];
for(var i =0;i<=n;i++){
if(i%3===0 || i%5===0 || i%7===0){
arr.push(i)
}
}
return arr;
}
return div;
})()
</script>
<body>
</body>
</html>
私有变量
严格来说,js中没有私有成员的概念。
所有对象属性都是公有的。
不过倒是有一个私有变量的概念。
任何函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。
私有变量包括函数的参数,局部变量和在函数内部定义的其他函数
function add(num1,num2){
var sum = num1+num2
return sum
}
在这个函数内部,有3个私有变量:num1,num2,sum、
在函数内部可以访问这几个变量,
但在函数外部则不能访问他们。
如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。
利用这一点,就可以创建访问私有变量的共有方法
我们把有权访问私有变量和私有函数的公有方法称为特权方法。
有两种在对象上创建特权方法的方式。
构造函数中定义特权方法
第一种是在构造函数中定义特权方法。
function MyObject(){
//私有变量和私有函数
var privateVariable = 0;
function privateFunction(){
return false
}
//特权方法
this.publicMethod = function(){
privateVariable ++;
return privateFunction()
}
}
能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。
对这个例子而言,变量privateVariable和函数privateFunction()只能通过publicMethod方法访问。
利用私有和特权成员,可以隐藏那些不应该被直接修改的数据。
例如
function Person(name){
this.getName = function(){
return name
}
this.setName = function(value){
name = value
}
}
var p = new Person('Tom')
p.getName()
p.setName('test')
在构造函数中定义了两个操作name的方法。
它们作为闭包能够通过作用域链访问name。
私有变量和Person中的每一个实例都不相同,
因为每次调用构造函数都会重新创建这两个方法。
不过在构造函数中定义特权方法有一个缺点(也是构造函数的缺点):对于每个实例都会创建同样一组方法。
而使用静态私有变量来实现特权方法就可以避免这个问题
基于原型定义静态私有变量
通过私有作用域中定义私有变量或函数,同样可以创建特权方法
(function(){
//私有变量和私有函数
var privateVariable = 10
function privateFunction(){
return false
}
//构造函数
//隐式声明全局变量
MyObject = function(){
}
//公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable ++;
return privateFunction()
}
})()
这里的特权方法是基于原型定义的
- 需要注意的是,这个模式在定义构造函数时并没有使用函数声明,而是使用了函数表达式。
- 函数声明只能创建局部函数,但那并不是我们想要的。
- 出于同样的原因,也没有用var声明MyObject。
- 初始化未经var声明的变量总是会创建一个全局变量。
- 因此MyObject成为了一个全局变量,能够在私有作用域之外被访问。
- 但要知道,在严格模式下给未经声明的变量赋值会导致错误。
- 这个模式与在构造函数中定义特权方法的主要区别:
- 主要区别在于私有变量和函数是由实例共享的。
- 由于特权方法是在原型上定义的,因此所有实例都共享同一个函数。
- 而这个特权方法,作为一个闭包,总是保存着对包含作用域的引用。
- 以这种方式创建静态私有变量会因为使用原型而增进代码复用。
- 但每个实例都没有自己的私有变量。
- 到底是使用实例变量还是静态私有变量,最终要看需求而定
多查找作用域链中的一个层次,就会在一定程度上影响查找速度,而这正是使用闭包和私有变量的一个鲜明的不足之处
模块模式
模块模式就是为单例创建私有变量和特权方法。
所谓单例,指的就是只有一个实例的对象。
按照惯例,js总是以对象字面量的方式创建单例对象。
var singleton = {
name:value,
method:function(){
//方法
}
}
模块模式通过为单例添加私有变量和特权方法能够使其得到增强。
var singleton = {
//私有变量和私有函数
var privateVariable = 10
function privateFunction(){
return false
}
//特权/公有方法和属性
return {
publicProperty:true,
publicMethod:function(){
privateVariable++
return privateFunction()
}
}
}()
//var test = function(){}() 正确执行
// function(){}() 报错
// ()左边必须是一个表达式
这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的。
简而言之,如果必须创建一个对象并以某些数据进行初始化,同时还要公开一些能够访问私有数据的方法,那么就可以使用模块模式。
增强的模块模式
进一步改进模块模式,即在返回对象之前加入对其增强的代码。
这种增强的模块模式适合那些单例必须是某种类型的实例,同时还添加某些属性或方法对其加强的情况
var singleton = {
//私有变量和私有函数
var privateVariable = 10
function privateFunction(){
return false
}
//* 创建对象
var object = new CustomType()
//特权/公有方法和属性
object.publicProperty=true
object.publicMethod=function(){
privateVariable++
return privateFunction()
}
}
//* 返回这个对象
return object
}()
//var test = function(){}() 正确执行
// function(){}() 报错
// ()左边必须是一个表达式