git文章

This commit is contained in:
结发受长生
2018-05-06 12:18:58 +08:00
parent 2e536826e2
commit fc3bc198b9
27 changed files with 1097 additions and 4 deletions
@@ -0,0 +1,80 @@
---
title: 函数形参与arguments
date: 2018-4-14 00:28:25
tags:
- JavaScript
- 函数
categories:
- JavaScript
---
在之前我们知道 , 在JS当中函数的形参与调用时传入的实参并不需要必须对应
在函数的内部有一个对象`arguments` , 这是一个类数组 , 其中包含调用时传入的实参
当然函数在调用的时候 , 会按照形参的位置给形参赋值
<!-- more -->
但是如果我们在函数内部对形参进行赋值 , 或者对arguments当中的元素进行赋值 , 会出现怎样的情况呢
这在一般模式和严格模式下会有不同的行为
### 一般模式
调用时对于没有对应到实参的形参进行赋值
```javascript
function func1(arg0, arg1) {
arg1 = 10;
console.log(arg1, arguments[1]);
}
func1(19);// 10 undefined
function func2(arg0, arg1) {
arguments[1] = 10;
console.log(arg1, arguments[1]);
}
func2(21);// undefined 10
```
上面两个函数都有两个形参 , 实际调用时传入了一个实参 , **arg1**是没有与实参对应的
这时候对形参赋值 , 或者对arguments当中对应的元素赋值 , 各自的行为都是**独立**的 , 彼此不会产生影响
形参与实参有对应的情况
```javascript
function func1(arg0) {
arg0 = 10;
console.log(arg0, arguments[0]);
}
func1(19);// 10 10
function func2(arg0) {
arguments[0] = 10;
console.log(arg0, arguments[0]);
}
func2(21);// 10 10
```
可以看到 , 在这种情况下 , 形参与arguments当中的元素是双向绑定的 , 修改一个会自动影响另一个
### 严格模式
在严格模式下同样执行与上面类似的代码
```javascript
"use strict";
function func1(arg0) {
arg0 = 10;
console.log(arg0, arguments[0]);
}
func1(19);// 10 19
function func2(arg0) {
arguments[0] = 10;
console.log(arg0, arguments[0]);
}
func2(21);// 21 10
function func3(arg0) {
arguments[0] = 10;
console.log(arg0, arguments[0]);
}
func3();// undefined 10
function func4(arg0) {
arg0 = 10;
console.log(arg0, arguments[0]);
}
func4();// 10 undefined
```
可见严格模式之下 , 无论是哪种情况 , arguments与形参都是独立的 , 不会进行绑定 , 之间互不影响
@@ -0,0 +1,114 @@
---
title: 变量的定义提升
date: 2018-4-14 00:30:18
tags:
- JavaScript
- 函数
categories:
- JavaScript
---
从一道笔试题说起
```javascript
function Foo() {
getName = function () { console.log (1); };
return this;
}
Foo.getName = function () { console.log (2);};
Foo.prototype.getName = function () { console.log (3);};
var getName = function () { console.log (4);};
function getName() { console.log (5);}
//请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
```
<!-- more -->
#### 第一问
Foo是一个函数对象 , `Foo.getName = … `在其中定义了一个名为getName的属性 , 也是一个函数
所以第一问当中的调用输出的应该是2
#### 第二问
这里涉及到了变量的定义提升问题 , 如果不了解容易误认为答案是5
例如如下语句
```javascript
console.log("x" in window);
var x;
```
虽然变量的定义是在第二个语句 , 但是输出的结果仍然是true
因为代码执行时 JS引擎会把变量的声明语句提升到最上方
但是提升的仅仅是变量声明 , 如果声明变量的时候包含初始化
则会将初始化作为单纯的一条赋值语句保留在原处
例如
```javascript
console.log("x" in window);
var x = 10;
```
在JS引擎处理之后会改变为
```javascript
var x;
console.log("x" in window);
x = 10;
```
> 补充说明 : 只有显式的变量定义才会被提升
> 隐式的变量定义并不会
> 比如
```
console.log("x" in window);//false
x = 10;
```
> 输出就是false , 因为x未定义
全局函数的定义与全局变量的定义本质是一样的
所以它也会被提升到顶部
但是 `var x = function(){}``function x(){}`两种定义方式最终效果相同
执行过程却是不同的
```javascript
console.log(x);//function x(){}
function x(){}
console.log(x);//function x(){}
```
或者
```javascript
console.log(x);//undefined
var x = function(){}
console.log(x);//function x(){}
```
那么具体到这个题目
代码
```javascript
var getName = function () { console.log (4);};
function getName() { console.log (5);}
```
会被JS引擎在解析时修改为
```javascript
var getName;
function getName() { console.log (5);}
getName = function () { console.log (4);};
```
显然 , 第三行的赋值会将第二行的定义覆盖掉
最终这个全局的getName函数输出的值是4
#### 第三问
Foo() 是在全局的基础上调用的 , 也就是由window对象调用的
所以return this返回的是window对象
但是在这个函数的内部 , 修改了全局的getName的值
现在名为getName的全局函数输出是1了
所以再调用window.getName() 的输出当然是1
#### 第四问
同样是调用全局的getName函数 , 所以结果和第三问一样 , 也是1
#### 第五问
成员访问的点号( . )优先级高于 new , 所以结果与第一问相同 , 也是2
#### 第六问
括号运算符的优先级高于点号( . )
实际执行为 `( new Foo( ) ).getName()`
所以调用的是原型对象上的getName函数 , 输出是3
@@ -0,0 +1,87 @@
---
title: 实现bind方法
date: 2018-4-14 00:35:19
tags:
- JavaScript
- prototype
categories:
- JavaScript
---
bind方法来自于`Function.prototype`
这个方法会创建一个新函数 , 当这个函数被调用时 , 第一个参数将会作为它运行时的this , 之后的参数会作为实际调用时传递的实参前作为实参
<!-- more -->
语法
<pre>
func.bind(thisArg [,arg1[,arg2[, ...]]])
</pre>
**应用示例 :**
```javascript
var obj = {
name : "Sookie",
show : function() {
console.log(this.name);
console.log(arguments);
}
}
var _show = obj.show.bind(obj,"aa","bb");
_show("cc");
/*output:
Sookie
{ '0': 'aa', '1': 'bb', '2': 'cc' }
*/
```
如果要实现一个bind方法 , 需要用到柯里化
```javascript
Function.prototype.bind = function(context) {
var me = this;//调用bind的函数对象
var args = Array.prototype.slice.call(arguments, 1);//传入的固定实参
return function () {
//实际调用时传入的参数
var innerArgs = Array.prototype.slice.call(arguments);
//合并两个参数数组
var finalArgs = args.concat(innerArgs);
//调用该函数
return me.apply(context, finalArgs);
}
}
```
这里解释一下 , arguments并不是数组对象 , 而是**类数组**
+ 具有 : 指向对象元素的数字索引 , 以及length属性
+ 不具有 : push , slice等数组对象的方法
> 数组对象的\_\_proto\_\_是Array
而arguments的 \_\_proto\_\_是Object
如果要把一个arguments转化为一个数组对象 , 可以使用如下方式
<pre>
var args = Array.prototype.slice.call(arguments);
</pre>
#### 嗅探
在运行时如果对标准库当中的内容有修改
( 可能是出于兼容性的需要 , 比如旧版本的标准库当中无此函数 )
```javascript
Function.prototype.bind = Function.prototype.bind || function(context) {
//...
}
```
这属于一个加分项
由于标准库当中的函数多是经过了深度优化的
一般要比我们自己写的函数效率更高 更健壮
所以如果标准库中存在 , 其实没有必要用我们自己写的方法去替换
这里就是进行`嗅探`
> 这是一个典型的 `Monkey patch(猴子补丁)`
> 主要有以下几个用处
> + 在运行时替换方法 属性等
> + 在不修改第三方代码的情况下增加原来不支持的功能
> + 在运行时为内存中的对象增加patch而不是在磁盘的源代码中增加
#### 更严谨的做法
由于需要调用bind方法的一定是一个函数 , 所以有必要在bind的内部做一个校验
```javascript
if(typeof this !== "function") {
throw new TypeError("what is trying to be bound is not callable");
}
```
@@ -0,0 +1,86 @@
---
title: 实现call方法
date: 2018-4-14 00:56:44
tags:
- JavaScript
- prototype
categories:
- JavaScript
---
> call方法在使用一个指定的this值和若干个指定参数值的前提下调用某个函数
<!-- more -->
常规调用方式
```javascript
var obj = {
name : "Sookie",
func : function(msg) {
console.log(this.name + " : " + msg);
}
}
var fn1 = obj.func;
fn1.call(obj, "Hello");
```
现在如果要尝试实现一个call方法
#### 雏形
```javascript
Function.prototype.call2 = function(context) {
context.fn = this;
context.fn();
delete context.fn;
}
```
初步实现了不传参情况下的函数调用
但是还有不少缺陷
#### 对参数的处理
由于需要处理实际参数 , 而且实际参数的数量又是不确定的
所以我们可以考虑使用`eval`来执行这个函数
```javascript
Function.prototype.call2 = function(context) {
context.fn = this;
var args = [];
for(let i=1 ; i<arguments.length ; i++) {
args.push("arguments[" + i + "]");
}
eval("context.fn(" + args + ")");
delete context.fn;
};
```
比如实际调用函数的时候需要传入2个参数 , 包括前面的context , 所以arguments当中就有3个元素
那么就相当于执行
context.fn(arguments[1], arguments[2])
#### 返回值与完善
还需要处理函数的返回值
另外 , 在我们的call方法当中 , 对上下文对象添加了fn属性 , 最后又删除了它
如果这个对象上原本就有这个属性 , 那么就对这个对象产生了破坏
可以预先把这个对象保存下来
```javascript
Function.prototype.call2 = function(context) {
var _fn = null;
var flag = false;
if("fn" in context) {
_fn = context.fn;
}
_fn = context.fn;
context.fn = this;
var args = [];
for(let i=1 ; i<arguments.length ; i++) {
args.push("arguments[" + i + "]");
}
var result = eval("context.fn(" + args + ")");
if(flag) {
context.fn = _fn;
} else {
delete context.fn;
}
return result;
};
```