学在前面
1.call apply bind的区别
- call apply bind 都能改变this的指向, call、apply会立即执行, bind不会立即执行。
- call和apply不会返回新函数立即执行,bind会返回一个新函数不会立即执行。
- call和apply绑定一次之后下次在用的时候仍然需要绑定, bind返回的是一个新函数 且this不会在改变。
- call 跟apply的区别:call后面跟着的是参数列表,apply后面的参数是个数组,call的性能要高于apply。
概念部分
-
默认绑定
- 非严格模式下 this指向window(全局对象), 严格模式下this 指向undefined
let f = function () { console.log(this) // window }
2. 严格模式([use strict](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode))let f = function () { "use strict" console.log(this) // undefined }
- 非严格模式下 this指向window(全局对象), 严格模式下this 指向undefined
-
隐式绑定
- 如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上:
var name = 'lisi'; var obj = { name: 'zhangsan', fn: function () { console.log(this.name) } } obj.fn(); // zhangsan- 如果函数前面有多个函数调用的话,就近原则
var name = 'lisi'; var obj = { name: 'zhangsan', fn: function () { console.log(this.name) } } window.obj.fn(); // 这里输出的依旧是zhangsan- 如果我们把这个函数复制给一个变量在执行, 这里相当云window.方法, 回去找window下面的全局属性 如果存在 那就是window下面的值 否则就是undefined.
var name = 'lisi'; var obj = { name: 'zhangsan', fn: function () { console.log(this.name) } } let fn = obj.fn; fn() // 这里的值 就是lisi 并不是zhangsan -
显示绑定
let cal = { name: 'zhangsan' }; let app = { name: 'lisi' }; let bin = { name: 'wangerma' } function fn () { console.log(this.name) }; fn() // undefined fn.call(cal) // zhangsan fn.apply(app) // lisi fn.bind(bin)() // wangerma- 如果我更改了上面代码的执行顺序, bind放在call的上面, 输入的值是一样的, bind返回一个新的函数,this不可更改
fn = fn.bind(cal) fn() // wangerma fn.apply(app) // wangerma fn.call(bin)() // wangerma- 如果call apply bind 传入的参数是null 或者 undefined 内部的this仍指向window call和apply绑定之后,如果在调用还需要绑定, bind返回一个新的函数 且内部this的指向永远都不会改变。
var name = 'xiaotaoqi'; // 如果把这个var 换成let 你会发现不一样的结果 let cal = { name: 'zhangsan' }; let app = { name: 'lisi' }; let bin = { name: 'wangerma' } function fn () { console.log(this.name) }; fn() // xiaotaoqi fn.call(null) // xiaotaoqi fn.apply(undefined) // xiaotaoqi fn.call(app) // lisi fn() // xiaotaoqi fn = fn.bind(bin) fn() // wangerma fn() // wangerma- js的 Api(数组中的一些方法等等)中也有部分方法绑定了this
let obj = { name: 'xiyangyang' } [{name: 'huitailang'}, {name: 'hongtailang'}].forEach(function(){ console.log(this.name) // xiyangyang }, obj)- new关键字会进行如下操作:
- 创建一个空的简单JavaScript对象(即{});
- 链接该对象(设置该对象的constructor)到另一个对象 ;
- 将步骤1新创建的对象作为this的上下文 ;
- 如果该函数没有返回对象,则返回this。
function Person () { this.name = 'dahuilang' } var person = new Person(); console.log(person.name) // 大灰狼 -
- 先简单的说下概念:
- 更短的函数
- 没有单独的this
- 严格模式在箭头函数中不起作用
- 通过call apply bind 不能修改函数中this的指向
- 不能绑定arguments(这里可以用剩余运算符 ... 获取对应传入的参数) ,super或new.target
- 不能作为构造构造器,不能与new 一起使用
- 不能使用prototype属性
- 通常情况下 yield关键字不能在箭头中使用,因此建有函数不能用作函数的生成器
function Person() { this.age = 0; setInterval(function() { console.log(this.age) // 这里的this指向的是window 所以值是undefined }, 1000); } new Person()- 解决setTimeout中this指向的问题
function Person() { this.age = 0; setInterval(() => { console.log(this.age) // 这里的this 指向上一层的语法环境 所以指向的是Person类new出来的实例对象 }, 1000); } new Person() - 先简单的说下概念:
-
(function(){ console.log(this) // 非严格模式下是this, 严格模式下是undefined })()
练习部分
var number = 5;
var obj = {
number: 3,
fn1:(function () {
var number;
this.number *= 2;
number = number * 2;
number = 3;
return function () {
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
})()
}
var fn1 = obj.fn1;
fn1.call(null)
obj.fn1()
console.log(window.number)
答案: 10 9 3 27 20
解题步骤:
- 主执行栈执行
- obj.fn 是一个IIFE函数 所以会立即执行, 函数中的this指向window
- this.number *= 2 全局的number 变成了10
- number = 3 这个时候局部变量的number变成了3
- 返回了一个新的函数 作为obj.fn的函数 新函数里面的number就是调用自执行函数定义的number 形成了闭包
var number = 10; var obj = { number: 3, fn1:(){ var num = this.number; this.number *= 2; console.log(num); number *= 3; console.log(number); } } } var fn1 = obj.fn1; fn1.call(null) obj.fn1() console.log(window.number) - 执行 fn1.call(null),因为传入的null 此时fn1 里面的this指向window 所以num = 10 全局变量 this.number * 2 之后变了20, 此时外层的number 变成了20 必包中的number之前是3 所以变成了 9 所以输出的值为 20 9, 执行完之后代码变成
var number = 20;
var obj = {
number: 3,
fn1:(){
// 必包中的number 变成了9
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
}
}
- obj.fn1()调用的时候 fn1 里面的this指向了obj, 此时num = 3, this.number * 2 = 6, 必包作用域中的number *= 3 变成了27 所以输出结果为 3 27, 执行之后代码变成
var number = 20;
var obj = {
number: 6,
fn1:(){
// 必包中的number 变成了27
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
}
}
- window.numbwe 此时的结果为20
capp apply bind的简单实现
const getContext = (context) => {
context = context || window;
let type = typeof context;
if (type !== 'object') { // 如果是基本类型 包装成引用类型
context = Object(context)
}
return context
}
- call简单实现
Function.prototype.call = function (context, ...args) {
context = getContext(context);
let fn = Symbol('fn');
context[fn] = this;
let result = context[fn](...args);
delete context[fn];
return result;
}
- apply简单实现
Function.prototype.apply = function (context, args) {
context = getContext(context);
let fn = Symbol('fn');
context[fn] = this;
let result = context[fn](...args);
delete context[fn];
return result;
}
- bind简单实现
Function.prototype.bind = function (context, ...outerArgs) {
return (...innerArgs) => this.call(context, ...outerArgs, ...innerArgs)
}