四、函数
(1)函数参数:
Js中所有的参数都是按值传递的。
基本类型的传参就是复制。参数相当于局部变量。
引用类型传参,其实也是复制,不过复制的是对象的地址。
所以如果在函数内部改变了参数变量指向的对象并不会影响到外部。
例子:
var person = new Object();
function setName(obj) {obj.name = "liuchengyuan";obj = new Object();obj.name = "nauy";
}
setName( person );
console.log( person.name ) // 结果???
(2)执行环境:
定义变量或函数有权访问的其他数据。
定义变量和函数能被访问的范围。
执行环境中的所有代码执行完后,该环境被销毁,保存的变量和函数定义也被销毁。
(3)作用域:
变量或函数的有效范围,超出范围无效。
能被访问的范围。
理解:
作用域是由执行环境决定的。
(4)全局执行环境:
根据宿主环境不同,会有不同。浏览器中是window对象。
(5)变量对象:
每个执行环境都有一个与之关联的变量对象,
环境中所有变量函数都保存在这个对象中。
例子:
1 function fn1() { //fn1的执行环境就是代码块内的范围 2 3 console.log(a) 4 5 var a = 1; //fn1的执行环境决定了变量a的作用范围 6 7 console.log(a) 8 9 var b = 0; 10 11 function fn2() { 12 13 //新的执行环境 14 15 var a = 0; 16 17 console.log(b);//0 18 19 } 20 21 fn2 (); 22 23 } 24 25 fn 1();
(6)作用域链:
保证了函数和变量对执行环境的有序访问。
作用域链的第一个始终是当前执行的代码所在环境的变量对象。
作用域链的最后一个是window对象。
本质上就是一个指向变量对象的的指针列表。
//执行fn1时执行环境解析(域解析)后的变量对象
fn1Obj : {
a : 1,
b : 2,
fn2 : function(){}
}
//执行fn2时执行环境解析(域解析)后的变量对象
fn2Obj : {
a : 1,
parent : fn1Obj
}
在fn2函数执行的时候,首先会从fn2的执行环境里去找,
如果没有找到则会在上一级的执行环境里去找。
(7)解析器解析执行环境:
解析器解析一个执行环境时可简单的理解成两个步骤,
实际上要比这个复杂的多。
注意:这里的变量指的是es5及之前用var声明的变量
es6中的let有所不同,但原理应该是一样的。
1、预解析
将以 var 开头的 变量 提升。初始值均为undefined。
var :
a = undefined
b = undefined
将函数提升。
fn2 = function fn2() {}
2、逐行执行代码
执行第一行代码: 输出 undefined
执行第二行代码: a = 1
执行第三行代码: 输出 1
执行第四行代码: b = 0
声明函数的部分跳过。。。。(函数不会主动执行)
执行fn2时将 重复上面的步骤。
(8 )Es6之前是没有块作用域的:
if for while 的代码块不是块作用域。
解决办法: 使用匿名函数自执行。
(9)定义函数的几种方式
1、函数声明
function fn(){ }
2、函数表达式
var fn = function () {};
3、匿名函数自执行(立即调用的函数表达式,其实是一个表达式)
(function(){
console.log("ok")
})();
原理:匿名函数附近使用括号或一些一元运算符的惯用法,就是来引导解析器,指明运算符附近是一个表达式。
所以你会看到千奇百怪的写法:
(function(){console.log("ok")}());
!function(){console.log("ok")}();
。。。。。。。。。
这几种定义方式的区别和作用????
(10)与函数(方法、事件函数)有关的常用的几个对象和方法
this: 指向问题。
this所在当前方法或函数属于谁???
例一:
function sayThis() {
console.log(this)
}
sayThis()// window 如果是模块写法则是undefined
例二:
var obj = new Object();
obj.name = "liuchengyuan"
obj.sayThis = sayThis;
function sayThis() {
console.log(this.name)
}
obj.sayThis() //"liuchengyuan"
例三:
var obj = new Object();
obj.name = "liuchengyuan"
obj.sayThis = sayThis;
function sayThis() {
function fn(){
console.log(this.name)
}
fn()
}
obj.sayThis() //报错
arguments: 内部使用函数参数的集合。
function add() {
var r = 0;
for(var i=0, l=arguments.length; i<l; i++){
r += arguments[i];
}
return r;
}
console.log(add(2,564,4564,86,35456))//40672
event: 后面再详说。
改变方法内部指向。
call: 第一个参数是this绑定的对象,第二个参数开始是方法的参数。
apply: 第一个参数是this绑定的对象,第二个参数是一个数组。
bind: 与上面的区别是不会立即执行,而是返回一个新的函数。
(11)递归
函数在内部调用自身。
function out(i) {
if( i == 0 ) return false;
console.log(i);
out(--i);
}
out(10);
这种写法存在的问题:
在函数内部使用了函数名out, 如果函数名改了将会出问题。
var f = out;
out = null;
f(10);
解决办法:
1、使用内部指向正在执行的函数的指针。arguments.callee()。
但 在严格模式禁用。
2、将函数定义解析成表达式
var f = (
function out(i) {
if( i == 0 ) return false;
console.log(i);
out(--i);
}
);
f(10);
(12)闭包
定义: 有权访问另一个函数作用域的函数。
关键字: 闭包就是函数。
从某种意义上来说js中所有函数都是闭包。
特点:
内部函数引用外部函数的变量或函数时, 内部执行环境由于某种原因一直不销 毁,这就会造成外部执行环境销毁时会保留变量对象。
所以使用闭包会比普通函数占有更多的内存。
副作用:
如果内部函数引用的外部函数的变量值是在变化的,那闭包只能获取最后一个 值。
方法闭包内的this指向问题:
在某个对象的方法内使用闭包,则闭包内的this不在指向这个对象。
为什么????
解决办法:
把this存起来,然后给闭包传参。
例一:
var arr = [];
for(var i=0; i<10; i++) {
arr[i] = function() {
return i;
}
}
console.log(arr[0]()) //10
例二:
var arr = [];
for(var i=0; i<10; i++) {
arr[i] = (function(i) {
return i;
})(i);
}
console.log(arr[0]) //
例子三:
function Set_ () {
var arr = [];
function get() {
return JSON.parse(JSON.stringify(arr));
};
function has(v) {
if(indexOf(v) == -1)
return false;
return true;
}
function indexOf(v) {
for(var i=0, l=arr.length; i<l; i++){
if(arr[i] === v) return i;
}
return -1;
}
function add(v) {
if(indexOf(v) == -1){
arr.push(v);
}else {
//throw new Error("数组中已存在这个值");
console.log("数组中已存在这个值")
}
}
function remove(v) {
var index = indexOf(v);
if( index != -1){
arr.splice(index,1);
}else {
//throw new Error("数组中不存在这个值");
console.log("数组中已存在这个值")
}
}
function toString() {
return arr.toString();
}
return {
add : add,
remove : remove,
has : has,
getArr : get,
toString : toString
}
}
思考?? Vuex是怎么实现单一状态树的????
(13)执行字符串形式的js语句
1、eval("alert(1)")
2、new Function("alert(1)")()
3、手动引入js文件,跟jsonp的实现原理一样。