听说,函数是 JS 中的一等公民
原始类型作为参数传入函数没有关系,引用类型作为参数传入,传入的类似于 C 语言指针,会改变指针指向的地址内的值。
function myFunc(theObject) {
theObject.make = 'Toyota';
}
var mycar = { make: 'Honda', model: 'Accord', year: 1998 };
var x, y;
x = mycar.make; // x获取的值为 "Honda"
myFunc(mycar);
y = mycar.make; // y获取的值为 "Toyota"
// (make属性被函数改变了)
const square = function (number) {
return number * number;
};
var x = square(4); // x gets the value 16
const factorial = function fac(n) {
return n < 2 ? 1 : n * fac(n - 1);
};
console.log(factorial(3));
将函数作为参数传递给另一个函数时,函数表达式很方便。
function map(f, a) {
let result = []; // 创建一个数组
let i; // 声明一个值,用来循环
for (i = 0; i != a.length; i++) result[i] = f(a[i]);
return result;
}
const f = function (x) {
return x * x * x;
};
let numbers = [0, 1, 2, 5, 10];
let cube = map(f, numbers);
console.log(cube);
条件定义
var myFunc;
if (num == 0) {
myFunc = function (theObject) {
theObject.make = 'Toyota';
};
}
new Function('a', 'b', 'return a + b');
每个 JavaScript 函数实际上都是一个 Function 对象。
(function () {}.constructor === Function); // true
实例方法有 bind(), call(), apply(), toString();
var x = 10;
function createFunction1() {
var x = 20;
return new Function('return x;'); // 这里的 x 指向最上面全局作用域内的 x
}
function createFunction2() {
var x = 20;
function f() {
return x; // 这里的 x 指向上方本地作用域内的 x
}
return f;
}
var f1 = createFunction1();
console.log(f1()); // 10
var f2 = createFunction2();
console.log(f2()); // 20
在函数内定义的变量不能在函数之外的任何地方访问,一个函数可以访问定义在其范围内的任何变量和函数。
一个函数可以指向并调用自身。有三种方法可以达到这个目的:
var foo = function bar() {
// statements go here
};
// 在这个函数体内,以下的语句是等价的:
bar();
arguments.callee();
foo();
// 递归需要一个终止条件
function loop(x) {
if (x >= 10)
// "x >= 10" 是退出条件(等同于 "!(x < 10)")
return;
// 做些什么
loop(x + 1); // 递归调用
}
loop(0);
//有些算法并不能简单的用迭代来实现。例如,获取树结构中所有的节点时,使用递归实现要容易得多:
function walkTree(node) {
if (node == null)
//
return;
// do something with node
for (var i = 0; i < node.childNodes.length; i++) {
walkTree(node.childNodes[i]);
}
}
function foo(i) {
if (i < 0) return;
console.log('begin:' + i);
foo(i - 1);
console.log('end:' + i);
}
foo(3);
// 输出:
// begin:3
// begin:2
// begin:1
// begin:0
// end:0
// end:1
// end:2
// end:3
// 可以看到压栈和弹出的顺序
可以在一个函数里面嵌套另外一个函数
嵌套(内部)函数对其容器(外部)函数是私有的。它自身也形成了一个闭包。一个闭包是一个可以自己拥有独立的环境与变量的表达式(通常是函数)。
既然嵌套函数是一个闭包,就意味着一个嵌套函数可以”继承“容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。
function addSquares(a, b) {
function square(x) {
return x * x;
}
return square(a) + square(b);
}
a = addSquares(2, 3); // returns 13
b = addSquares(3, 4); // returns 25
c = addSquares(4, 5); // returns 41
由于内部函数形成了闭包,因此你可以调用外部函数并为外部函数和内部函数指定参数:
function outside(x) {
function inside(y) {
return x + y;
}
return inside;
}
fn_inside = outside(3); // 可以这样想:给一个函数,使它的值加3
result = fn_inside(5); // returns 8
result1 = outside(3)(5); // returns 8
一个闭包必须保存它可见作用域中所有参数和变量。因为每一次调用传入的参数都可能不同,每一次对外部函数的调用实际上重新创建了一遍这个闭包。只有当返回的 inside 没有再被引用时,内存才会被释放。
这与在其他对象中存储引用没什么不同,但是通常不太明显,因为并不能直接设置引用,也不能检查它们。
函数可以被多层嵌套,故而形成多个闭包,因此闭包可以包含多个作用域。
多层闭包递归包含了所有包含它的函数作用域,这个被称为作用域链。
当同一个闭包作用域下两个参数或变量同名,就会产生命名冲突。更近的作用域有更高的优先级。
遵循就近原则。
闭包是 JavaScript 中最强大的特性之一。
由于内部函数可以访问外部函数的作用域,因此当内部函数生命周期大于外部函数时,外部函数中定义的变量和函数的生命周期将比内部函数执行时间长。当内部函数以某种方式被任何一个外部作用域访问时,一个闭包就产生了。
var createPet = function (name) {
var sex;
return {
setName: function (newName) {
name = newName;
},
getName: function () {
return name;
},
getSex: function () {
return sex;
},
setSex: function (newSex) {
if (
typeof newSex == 'string' &&
(newSex.toLowerCase() == 'male' || newSex.toLowerCase() == 'female')
) {
sex = newSex;
}
},
};
};
var pet = createPet('Vivie');
pet.getName(); // Vivie
pet.setName('Oliver');
pet.setSex('male');
pet.getSex(); // male
pet.getName(); // Oliver
上述代码中,外部函数 name 变量对内嵌函数来说是可见的,而除了通过内嵌函数本身,没有其他任何方法可以取得内嵌的变量。内嵌函数的内嵌变量就像内嵌函数的保险柜。它们会为内嵌函数保留“稳定”而又安全的数据参与运行。
最后,闭包要避免命名冲突。
函数实参会被保存在一个类似数组的 arguments 对象中。在函数内,可以访问 aguments 来获取实参。
使用 arguments 就使得函数参数可变。
function myConcat(separator) {
var result = ''; // 把值初始化成一个字符串,这样就可以用来保存字符串了!!
var i;
// iterate through arguments
for (i = 1; i < arguments.length; i++) {
result += arguments[i] + separator;
}
return result;
}
arguments 只是一个类似于数组的对象,而不是数组,类似数组是指它有索引编号和.length 属性。
函数参数默认值是 undefined
函数默认参数可修改。
function multiply(a, b = 1) {
return a * b;
}
multiply(5); // 5
剩余参数语法允许将不确定数量的参数表示为数组。
function multiply(multiplier, ...theArgs) {
return theArgs.map((x) => multiplier * x);
}
var arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]
相对于函数有较短的语法并以词法的方式绑定 this。
在箭头函数出现之前,每一个新函数都重新定义了自己的 this 值。
箭头函数捕捉闭包上下文的 this 值。
function Person() {
this.age = 0;
setInterval(() => {
this.age++; // 这里的`this`正确地指向person对象
}, 1000);
}
var p = new Person();
eval()方法会对一串字符串形式的 JavaScript 代码字符求值。
uneval()方法创建的一个 Object 的源代码的字符串表示。
isFinite()函数判断传入的值是否是有限的数值。
如果需要的话,其参数首先被转换为一个数值。
isNaN()函数判断一个值是否是 NaN。
注意:isNaN 函数内部的强制转换规则十分有趣;
另一个可供选择的是 ECMAScript 6 中定义 Number.isNaN() , 或者使用 typeof 来判断数值类型。
parseFloat() 函数解析字符串参数,并返回一个浮点数。
parseInt() 函数解析字符串参数,并返回指定的基数(基础数学中的数制)的整数。
decodeURI() 函数对先前经过 encodeURI 函数或者其他类似方法编码过的字符串进行解码。
decodeURIComponent()方法对先前经过 encodeURIComponent 函数或者其他类似方法编码过的字符串进行解码。
encodeURI()方法通过用以一个,两个,三个或四个转义序列表示字符的 UTF-8 编码替换统一资源标识符(URI)的某些字符来进行编码(每个字符对应四个转义序列,这四个序列组了两个”替代“字符)。
encodeURIComponent() 方法通过用以一个,两个,三个或四个转义序列表示字符的 UTF-8 编码替换统一资源标识符(URI)的每个字符来进行编码(每个字符对应四个转义序列,这四个序列组了两个”替代“字符)。
已废弃的 escape() 方法计算生成一个新的字符串,其中的某些字符已被替换为十六进制转义序列。使用 encodeURI 或者 encodeURIComponent 替代本方法。
已废弃的 unescape() 方法计算生成一个新的字符串,其中的十六进制转义序列将被其表示的字符替换。上述的转义序列就像 escape 里介绍的一样。因为 unescape 已经废弃,建议使用 decodeURI()或者 decodeURIComponent 替代本方法。
多层闭包递归包含了所有包含它的函数作用域,这个被称为作用域链。
闭包要注意命名冲突
箭头函数没有自己的 this,所以会捕获上下文的 this
突然想起来面试时,有个问题忘了记录
严格模式可避免
function foo() {
bar = 2;
console.log('bar没有被声明!');
}
很多时候,为了方便存取,经常会将 DOM 结点暂时存储到数据结构中.但是在不需要该 DOM 节点时,忘记解除对它的引用,则会造成内存泄露.
var element = {
shotCat: document.getElementById('shotCat'),
};
document.body.removeChild(document.getElementById('shotCat'));
// 如果element没有被回收,这里移除了 shotCat 节点也是没用的,shotCat 节点依然留存在内存中.
与此类似情景还有: DOM 节点绑定了事件, 但是在移除的时候没有解除事件绑定,那么仅仅移除 DOM 节点也是没用的
var someResource = getData();
setInterval(function () {
var node = document.getElementById('Node');
if (node) {
node.innerHTML = JSON.stringify(someResource);
}
}, 1000);
如果没有清除定时器,那么 someResource 就不会被释放,如果刚好它又占用了较大内存,就会引发性能问题. 但是 setTimeout ,它计时结束后它的回调里面引用的对象占用的内存是可以被回收的. 当然有些场景 setTimeout 的计时可能很长, 这样的情况下也是需要纳入考虑的.
function foo() {
var a = {};
function bar() {
console.log(a);
}
a.fn = bar;
return bar;
}
bar 和 a 形成了相互循环引用。bar 作为一个闭包,即使它内部什么都没有,foo 中的所有变量都还是隐式地被 bar 所引用。
即使 bar 内什么都没有还是造成了循环引用,那真正的解决办法就是,不要将 a.fn = bar.
评论0