方法快速查询

说明

image.png
image.png

命名规则

1.必须使用字母、下划线(_)或者美元符($)开始。

2.可以使用任意多个英文字母、数字、下划线(_)或者美元符($)组成。

3.不能使用JavaScript关键词与JavaScript保留字。

4.变量和函数重名,函数优先,变量没办法用

5.对大小写敏感,function 必须小写

==================

创建函数

使用 const 比使用 var 更安全,因为函数表达式始终是常量值。
image.png

1、声明写法

  1. function a(a, b) {
  2. return a * b;
  3. }
  4. a(1,2);

函数声明提升

  1. // 没问题
  2. console.log(sum(10, 10));
  3. function sum(num1, num2) {
  4. return num1 + num2;
  5. }

2、表达式写法

  1. const x = function a(a, b) { // 或者用let
  2. return a * b;
  3. }
  4. x(1,2);

3、匿名写法

  1. var x = function (a, b) {
  2. return a * b
  3. }; //匿名函数,没有名字的
  4. x(4, 3);

4、箭头函数写法 ES6

https://www.yuque.com/yejielin/mypn47/gis917

==================

this

https://www.yuque.com/yejielin/mypn47/gis917

=================

调用

立刻调用

  1. (function () {
  2. var x = "Hello!!"; // 我将立刻调用自己
  3. })();
  4. //有参数的
  5. var i = 0;
  6. (function(i) {
  7. console.log("Hello!!"+i); // 我将立刻调用自己
  8. })(i);

image.png

参数

可以多可以少,不影响执行
image.png
image.png

1、值传递

image.png
如上图,打印的temp为”why”,是因为变量是存在栈空间,函数test也是存在栈空间,对于name的修改只是在栈空间内的函数内部进行,函数调用完就删掉了,因此没有改变temp的值。
image.png

2、引用传递

image.png
引用数据就不一样,因为具体数据是储存在堆空间,调用函数直接改的是堆空间的数据,就算函数销毁了,堆空间的数据一样是发生了变化
image.png

函数当做参数(回调)

  1. function f1(s){
  2. s();
  3. };
  4. f2 = function(){
  5. console.log(123)
  6. };
  7. f1(f2);

name 属性

每个函数定义后都有一个name属性,表示属性的名字。

  1. function x(a,b){
  2. console.log(a,b);
  3. }
  4. console.log(x.name);

arguments 对象(不推荐)

JavaScript还有一个免费赠送的对象arguments,它只在函数内部起作用(箭头函数没有),并且永远指向当前函数的调用者传入的所有参数,可以用于判断参数的个数。

js支持实参超过形参的情况,所有实参都会存入arguments 对象内

类似数组,但不是数组。

  1. function foo(x) {
  2. console.log('x = ' + x); // 10
  3. for (var i=0; i<arguments.length; i++) {
  4. console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
  5. }
  6. }
  7. foo(10, 20, 30);
  8. //可以用数组的方法
  9. var sum = function(x){
  10. for(i of arguments){
  11. console.log(i);
  12. }
  13. }
  14. sum(1,2,3,4,5)
  15. //1,2,3,4,5

严格模式:
1、在函数重新设置arguments[x]的值,就算修改了,传入的参数还是原来的值,也就是无法通过函数内部修改传入的参数的值。
2、在函数中尝试重写arguments 对象会导致语法错误。(代码也不会执行。)

callee 对象所在函数

让函数逻辑与函数名解耦

  1. // 阶乘
  2. function factorial(num) {
  3. if (num <= 1) {
  4. return 1;
  5. } else {
  6. return num * factorial(num - 1); // 重复调用自己,但要正确执行就必须保证函数名是 factorial
  7. //或
  8. return num * arguments.callee(num - 1); // 不需要在意自己的函数名
  9. }
  10. }

转成数组

  1. function a(){
  2. // 通过 内置对象Array 原型中的slice方法,调用,转成数组
  3. let arguArray = Array.prototype.slice.call(arguments)
  4. // 或
  5. let arguArray2 = Array.from(arguments)
  6. // 或
  7. let arguArray3 = [...arguments]
  8. console.log(arguArray)
  9. }

ES6 展开参数 *

image.png
image.png image.png

ES6 剩余参数 *

箭头函数也可以收集

  1. function foo(a, b, ...rest) {
  2. console.log('a = ' + a);
  3. console.log('b = ' + b);
  4. console.log(rest);
  5. }
  6. foo(1, 2, 3, 4, 5);
  7. // 结果:
  8. // a = 1
  9. // b = 2
  10. // Array [ 3, 4, 5 ]
  11. foo(1);
  12. // 结果:
  13. // a = 1
  14. // b = undefined
  15. // Array []

image.png

image.png
image.png

ES6 默认参数值*

image.png
image.png
ES6后可以直接写默认值
image.png

很多工具如babel,帮你转换成ES5,会这样转:
image.png

默认参数值 配合 解构*

原本
image.png

可以写成
image.png
image.png
image.png

注意事项

image.png
image.png(放在前面,不能省,只能放undefined)

image.png
image.png(打印2)

前面默认值的,会导致后面的参数都不算在length里面
image.png

暂时性死区

  1. // 调用时不传第一个参数会报错
  2. function makeKing(name = numerals, numerals = 'VIII') {
  3. return `King ${name} ${numerals}`;
  4. }
  5. // 调用时不传第二个参数会报错
  6. function makeKing(name = 'Henry', numerals = defaultNumeral) {
  7. let defaultNumeral = 'VIII';
  8. return `King ${name} ${numerals}`;
  9. }

各种形式的调用

1、直接调用

image.png
image.pngimage.png

2、call

image.png image.png

3、apply

image.pngimage.png

4、Reflect.apply

image.png image.png

5、递归调用

非严格模式:

  1. // 阶乘
  2. function factorial(num) {
  3. if (num <= 1) {
  4. return 1;
  5. } else {
  6. return num * factorial(num - 1); // 重复调用自己
  7. //或
  8. return num * arguments.callee(num - 1);// 严格模式无法读取arguments 的 callee属性
  9. }
  10. }

严格模式:命名函数表达式,是否严格模式都可以使用

  1. const factorial = (function f(num) {
  2. if (num <= 1) {
  3. return 1;
  4. } else {
  5. return num * f(num - 1);
  6. }
  7. });

=================

返回值 return

1.返回函数值

注意,return 后面的函数代码都不会执行。

  1. //如果没有return,那么返回undefined;
  2. //有return但是后面跟着没有值,也是undefined;
  3. function myFunction()
  4. {
  5. var x=5;
  6. return x;
  7. }
  8. //会返回x的值5,并退出函数,如下
  9. var y = myFunctiong(); //y=5

还可以返回函数,如:

  1. function y()
  2. {
  3. function x(){console.log("abc");}
  4. return x();
  5. }
  6. y();
  7. //或者
  8. function y()
  9. {
  10. function x(){console.log("abc");}
  11. return x;
  12. }
  13. y()();

2.退出函数

注意,return 后面的函数代码都不会执行。

=================

属性与方法

length 参数个数

  1. function sayName(name) {
  2. console.log(name);
  3. }
  4. function sum(num1, num2) {
  5. return num1 + num2;
  6. }
  7. function sayHi() {
  8. console.log("hi");
  9. }
  10. console.log(sayName.length); // 1
  11. console.log(sum.length); // 2
  12. console.log(sayHi.length); // 0

image.png
image.png(打印2)

前面默认值的,会导致后面的参数都不算在length里面
image.png

prototype 引用类型实例方法

查看类
JS - 类 class

call / apply 调用函数

image.png
image.png image.png
image.pngimage.png

第一个参数:函数内 this 的值

在严格模式下,调用函数时如果没有指定上下文对象,则 this 值不会指向 window。
除非使用 apply()或 call()把函数指定给一个对象,否则 this 的值会变成 undefined。

apply()和 call()真正强大的地方并不是给函数传参,而是控制函数调用上下文即函数体内 this值的能力。

  1. window.color = 'red';
  2. let o = {
  3. color: 'blue'
  4. };
  5. function sayColor() {
  6. console.log(this.color);
  7. }
  8. sayColor(); // red
  9. sayColor.call(this); // red
  10. sayColor.call(window); // red
  11. sayColor.call(o); // blue

bind

bind()方法会创建一个新的函数实例,其 this 值会被绑定到传给 bind()的对象。

  1. window.color = 'red';
  2. var o = {
  3. color: 'blue'
  4. };
  5. function sayColor() {
  6. console.log(this.color);
  7. }
  8. let objectSayColor = sayColor.bind(o); // 创建了一个新函数 objectSayColor()。
  9. objectSayColor(); // blue

私有变量

函数或块中的变量,都可以认为是私有的,因为在这个函数或块的外部无法访问其中的变量

  1. function add(num1, num2) {
  2. let sum = num1 + num2; // sum 、sum1、sum2都是私有变量
  3. return sum;
  4. }

特权方法

能够访问函数私有变量(及私有函数)的公有方法

  1. function MyObject() {
  2. // 私有变量和私有函数
  3. let privateVariable = 10;
  4. function privateFunction() {
  5. return false;
  6. }
  7. // 特权方法
  8. this.publicMethod = function() {
  9. privateVariable++;
  10. return privateFunction();
  11. };
  12. }

如下面的例子所示,可以定义私有变量和特权方法,以隐藏不能被直接修改的数据:

  1. function Person(name) {
  2. //特权方法
  3. this.getName = function() {
  4. return name;
  5. };
  6. //特权方法
  7. this.setName = function (value) {
  8. name = value;
  9. };
  10. }
  11. let person = new Person('Nicholas');
  12. // 通过特权方法访问并修改内部的属性name
  13. console.log(person.getName()); // 'Nicholas'
  14. person.setName('Greg');
  15. console.log(person.getName()); // 'Greg'

静态私有变量

特权方法也可以通过使用私有作用域定义私有变量和函数来实现

  1. (function() {
  2. // 私有变量
  3. let name = '';
  4. // 构造函数
  5. // 不使用关键字声明的变量会创建在全局作用域中,所以 Person 变成了全局变量,可以在这个私有作用域外部被访问
  6. // 注意在严格模式下给未声明的变量赋值会导致错误
  7. Person = function(value) {
  8. name = value; // 静态变量,可供所有实例使用
  9. // 这意味着在任何实例上调用 setName()修改这个变量都会影响其他实例。
  10. };
  11. // 公有和特权方法
  12. Person.prototype.getName = function() {
  13. return name;
  14. };
  15. Person.prototype.setName = function(value) {
  16. name = value;
  17. };
  18. })();
  19. let person1 = new Person('Nicholas');
  20. console.log(person1.getName()); // 'Nicholas'
  21. person1.setName('Matt');
  22. console.log(person1.getName()); // 'Matt'
  23. let person2 = new Person('Michael');
  24. console.log(person1.getName()); // 'Michael'
  25. console.log(person2.getName()); // 'Michael'

使用闭包和私有变量会导致作用域链变长,作用域链越长,则查找变量所需的时间也越多。

模块模式

在一个单例对象上实现了相同的隔离和封装。

单例对象(singleton)就是只有一个实例的对象。

模块模式是在单例对象基础上加以扩展,使其通过作用域链来关联私有变量和特权方法。

  1. let singleton = function() { // 模块模式使用了匿名函数返回一个对象
  2. // 私有变量和私有函数
  3. let privateVariable = 10;
  4. function privateFunction() {
  5. return false;
  6. }
  7. // 特权/公有方法和属性
  8. return {
  9. publicProperty: true,
  10. publicMethod() {
  11. privateVariable++;
  12. return privateFunction();
  13. }
  14. };
  15. }();

在 Web 开发中,经常需要使用单例对象管理应用程序级的信息。

  1. // 创建了一个application 对象用于管理组件
  2. let application = function() {
  3. // 私有变量和私有函数
  4. let components = new Array(); // 在创建这个对象之后,内部就会创建一个私有的数组 components
  5. // 初始化
  6. components.push(new BaseComponent()); // 将一个 BaseComponent 组件的新实例添加到数组中
  7. // 公共接口
  8. return {
  9. // 可以访问 components 私有数组的特权方法
  10. getComponentCount() { // 方法返回注册组件的数量
  11. return components.length;
  12. },
  13. registerComponent(component) { // 负责注册新组件
  14. if (typeof component == 'object') {
  15. components.push(component);
  16. }
  17. }
  18. };
  19. }();

模块增强模式

另一个利用模块模式的做法是在返回对象之前先对其进行增强。

这适合单例对象需要是某个特定类型的实例,但又必须给它添加额外属性或方法的场景。

  1. let singleton = function() {
  2. // 私有变量和私有函数
  3. let privateVariable = 10;
  4. function privateFunction() {
  5. return false;
  6. }
  7. // 创建对象
  8. let object = new CustomType();
  9. // 添加特权/公有属性和方法
  10. object.publicProperty = true;
  11. object.publicMethod = function() {
  12. privateVariable++;
  13. return privateFunction();
  14. };
  15. // 返回对象
  16. return object;
  17. }();
  1. let application = function() {
  2. // 私有变量和私有函数
  3. let components = new Array();
  4. // 初始化
  5. components.push(new BaseComponent());
  6. // 创建局部变量保存实例
  7. let app = new BaseComponent();
  8. // 公共接口
  9. app.getComponentCount = function() {
  10. return components.length;
  11. };
  12. app.registerComponent = function(component) {
  13. if (typeof component == "object") {
  14. components.push(component);
  15. }
  16. };
  17. // 返回实例
  18. return app;
  19. }();

=================

作用域

全局变量

在所有地方都起作用
函数内不使用var 声明,或在最外层函数外用var声明,不推荐使用。

局部变量

只在特定地方起作用,比如函数内的变量

参数作用域

如果给函数参数设置默认值,那么会形成一个新的作用域,用来保存参数的值
image.png

嵌套

  1. function foo() {
  2. var x = 1;
  3. function bar() {
  4. var y = x + 1; // bar可以访问foo的变量x!
  5. }
  6. var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
  7. }

重名

  1. function foo() {
  2. var x = 1;
  3. function bar() {
  4. var x = 'A';
  5. console.log('x in bar() = ' + x); // 'A'
  6. }
  7. console.log('x in foo() = ' + x); // 1
  8. bar();
  9. }
  10. foo();

命名

减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。

  1. // 唯一的全局变量MYAPP:
  2. var MYAPP = {};
  3. // 其他变量:
  4. MYAPP.name = 'myapp';
  5. MYAPP.version = 1.0;
  6. // 其他函数:
  7. MYAPP.foo = function () {
  8. return 'foo';
  9. };

块级作用域(ES6)

var 定义的变量,是没有块级作用域的概念,只能通过函数来区分作用域,如下图例子
ES6后新增的let,就可以定义块级变量,只在当前的作用域生效

  1. {
  2. var a = 1;
  3. let b = 2;
  4. }
  5. console.log(a,b);
  6. //a=1,提示b没有定义

解构赋值(ES6)

  1. var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
  2. let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
  3. var person = {
  4. name: '小明',
  5. age: 20,
  6. gender: 'male',
  7. passport: 'G-12345678',
  8. school: 'No.4 middle school',
  9. address: {
  10. city: 'Beijing',
  11. street: 'No.1 Road',
  12. zipcode: '100001'
  13. }
  14. };
  15. var {name, age, passport} = person;
  16. var {name, address: {city, zip}} = person;
  17. var {name, single=true} = person;
  18. ({x, y} = { name: '小明', x: 100, y: 200});
  19. //快速交换2个变量
  20. var x=1, y=2;
  21. [x, y] = [y, x]
  22. function buildDate({year, month, day, hour=0, minute=0, second=0}) {
  23. return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
  24. }
  25. buildDate({ year: 2017, month: 1, day: 1 });
  26. // Sun Jan 01 2017 00:00:00 GMT+0800 (CST)
  27. buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 });
  28. // Sun Jan 01 2017 20:15:00 GMT+0800 (CST)

闭包

闭包指的是有权访问父作用域的函数,即使在父函数关闭之后。

条件(同时)

1.函数内嵌套函数
2.内部函数调用外部函数的变量

缺点

持续占用内存,容易导致内存泄漏。注意要及时释放 f() = null

应用场景一:通过循环给页面上多个dom节点绑定事件
场景描述:假如页面上有5个button,要给button绑定onclick事件,点击的时候,弹出对应button的索引编号。

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. </head>
  6. <body>
  7. <button>Button0</button>
  8. <button>Button1</button>
  9. <button>Button2</button>
  10. <button>Button3</button>
  11. <button>Button4</button>
  12. <script type="text/javascript">
  13. var btns = document.getElementsByTagName('button');
  14. for (var i = 0, len = btns.length; i < len; i++) {
  15. function x(i) {
  16. btns[i].onclick = function() {
  17. alert(i);
  18. }
  19. };
  20. x(i);
  21. }
  22. </script>
  23. </body>
  24. </html>

闭包使用场景二:封装变量
如下,封装了局部变量counter,外部是没办法访问这个变量,只能通过特定函数 myFunction(n) 改变。

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title></title>
  6. </head>
  7. <body>
  8. <button id="" onclick="myFunction(12)">
  9. 12
  10. </button>
  11. <button id="" onclick="myFunction(13)">
  12. 13
  13. </button>
  14. <script>
  15. function x(i) {
  16. var counter = 0;
  17. function y(i) {return counter += i;};
  18. return y;
  19. }
  20. var add = x();
  21. console.log(x(12));
  22. //返回的是函数function y(){ return counter += i }
  23. function myFunction(num){
  24. console.log(add(num));
  25. //add() 相当于就是调用函数y(){ return counter += i }
  26. //然后counter的初始化和赋值,都从y()上一层的函数x()获取var counter = 0;
  27. //每运行一次myFunction(),add()的返回值都会+1,从0开始,而不是100.
  28. }
  29. </script>
  30. </body>

闭包使用场景三:延续局部变量的寿命
img对象经常用于数据上报,发现在一些低版本浏览器上存在bug,会丢失部分数据上报,原因是img是report函数中的局部变量,当report函数调用结束后,img对象随即被销毁,而此时可能还没来得及发出http请求,所以此次请求就会丢失。

  1. var report = (function() {
  2. var imgs = [];
  3. return function(src) {
  4. var img = new Image();
  5. imgs.push(img);
  6. img.src = src;
  7. }
  8. }())

闭包使用场景四:自定义js模块
https://www.bilibili.com/video/av56415846/?p=174

=====================

特殊函数

标签函数

把模板字符串的插值变量,作为函数的参数。

第一个参数默认是显示插值和插值之间连接的字符,然后组成数组。

其他参数就是插值

  1. function aaaa(array,b,c){
  2. console.log(array);
  3. console.log(b);
  4. console.log(c);
  5. }
  6. // 标签函数
  7. aaaa`${1}+${2}`
  8. // array 为 ["", "+", ""]
  9. // b 为 1
  10. // c 为 2

==================

尾调用优化

  1. function outerFunction() {
  2. return innerFunction(); // 尾调用
  3. }

在 ES6 优化之前,执行这个例子会在内存中发生如下操作。
(1) 执行到 outerFunction 函数体,第一个栈帧被推到栈上。
(2) 执行 outerFunction 函数体,到 return 语句。计算返回值必须先计算 innerFunction。
(3) 执行到 innerFunction 函数体,第二个栈帧被推到栈上。
(4) 执行 innerFunction 函数体,计算其返回值。
(5) 将返回值传回 outerFunction,然后 outerFunction 再返回值。
(6) 将栈帧弹出栈外。

在 ES6 优化之后,执行这个例子会在内存中发生如下操作。
(1) 执行到 outerFunction 函数体,第一个栈帧被推到栈上。
(2) 执行 outerFunction 函数体,到达 return 语句。为求值返回语句,必须先求值 innerFunction。
(3) 引擎发现把第一个栈帧弹出栈外也没问题,因为 innerFunction 的返回值也是 outerFunction的返回值。
(4) 弹出 outerFunction 的栈帧。
(5) 执行到 innerFunction 函数体,栈帧被推到栈上。
(6) 执行 innerFunction 函数体,计算其返回值。
(7) 将 innerFunction 的栈帧弹出栈外。

第一种情况下每多调用一次嵌套函数,就会多增加一个栈帧。
而第二种情况下无论调用多少次嵌套函数,都只有一个栈帧。

优化在递归场景下的效果是最明显的,因为递归代码最容易在栈内存中迅速产生大量栈帧。

条件

1、代码在严格模式下执行;
2、外部函数的返回值是对尾调用函数的调用;
3、尾调用函数返回后不需要执行额外的逻辑;
4、尾调用函数不是引用外部函数作用域中自由变量的闭包。

  1. "use strict";
  2. // 无优化:尾调用没有返回
  3. function outerFunction() {
  4. innerFunction();
  5. }
  6. // 无优化:尾调用没有直接返回
  7. function outerFunction() {
  8. let innerFunctionResult = innerFunction();
  9. return innerFunctionResult;
  10. }
  11. // 无优化:尾调用返回后必须转型为字符串
  12. function outerFunction() {
  13. return innerFunction().toString();
  14. }
  15. // 无优化:尾调用是一个闭包
  16. function outerFunction() {
  17. let foo = 'bar';
  18. function innerFunction() { return foo; }
  19. return innerFunction();
  20. }
  21. // 有优化:栈帧销毁前执行参数计算
  22. function outerFunction(a, b) {
  23. return innerFunction(a + b);
  24. }
  25. // 有优化:初始返回值不涉及栈帧
  26. function outerFunction(a, b) {
  27. if (a < b) {
  28. return a;
  29. }
  30. return innerFunction(a + b);
  31. }
  32. // 有优化:两个内部函数都在尾部
  33. function outerFunction(condition) {
  34. return condition ? innerFunctionA() : innerFunctionB();
  35. }

例子:计算斐波纳契数列

  1. // 不满足条件
  2. function fib(n) {
  3. if (n < 2) {
  4. return n;
  5. }
  6. return fib(n - 1) + fib(n - 2);
  7. }
  8. console.log(fib(0)); // 0
  9. console.log(fib(1)); // 1
  10. console.log(fib(2)); // 1
  11. console.log(fib(3)); // 2
  12. console.log(fib(4)); // 3
  13. console.log(fib(5)); // 5
  14. console.log(fib(6)); // 8
  1. // 满足条件
  2. "use strict";
  3. // 基础框架
  4. function fib(n) {
  5. return fibImpl(0, 1, n);
  6. }
  7. // 执行递归
  8. function fibImpl(a, b, n) {
  9. if (n === 0) {
  10. return a;
  11. }
  12. return fibImpl(b, a + b, n - 1);
  13. }