一、 call的模拟实现
func.call(thisArg, arg1, arg2, ...) // call 用法 实现的原理:call() 改变this指向 并且执行func函数
所以模拟的步骤分三步完成:
- 将函数设为对象的属性
- 执行该函数
- 删除该函数
Function.prototype.callOwn = function(thisArg, args) {
// this指向调用callOwn 的对象 if (typeof this !== 'function') { // 调用callOwn 的若不是函数则报错 throw new TypeError('Error')
}
thisArg = thisArg || window // null时候执行window
thisArg.fn = this // 将调用callOwn 函数的对象添加到thisArg的属性中 const result = thisArg.fn(...[...arguments].slice(1)) // 执行该属性,
delete thisArg.fn // 删除该属性
return result
}thisArg.fn(...[...arguments].slice(1)) 这是对不定参数的处理,取出第二个到最后一个参数,进行函数调用赋值callOwn 案例走起
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.callOwn (foo, 'kevin', 18);
二、apply的模拟实现
func.apply(thisArg, [arg1, arg2, ...]) // apply 用法 Function.prototype.apply = function(thisArg, args) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
thisArg = thisArg || window
thisArg.fn = this
let result
if(args) {
result = thisArg.fn(...args)
} else {
result = thisArg.fn()
}
delete thisArg.fn
return result
}模拟实现的call和apply只是模拟了引用类型和null情况,针对基本类型没有做一个处理。
call和apply的注意点 这两个方法在调用的时候,如果我们传入数字或者字符串,这两个方法会把传入的参数转成对象类型。
var foo = 'string', number = 1
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this);
}
bar.call (foo, 'kevin', 18);
bar.apply(number,['kevin', 18])三、new的模拟实现
- 创建一个新对象
- 将新对象的proto指向原对象的 prototype
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回这个新对象
function new() {
// 创建一个新的对象
const obj = {}
// 获取第一个参数,arguments是类数组,不可直接调用shift方法
//此外因为 shift 会修改原数组,所以 arguments 会被去除第一个参数
const Constructor = [].shift.call(arguments)
// 将obj的原型指向构造函数的原型对象,这样obj就可以访问构造函数原型上的属性
obj.__proto__ = Constructor.prototype
// 将构造函数的this指向obj,这样obj就可以访问到构造函数中的属性
const res = Constructor.apply(obj, arguments);
return typeof res === 'object' ? res : obj;
}案列走起
function New() {
const obj = {}
const Constructor = [].shift.call(arguments)
obj.__proto__ = Constructor.prototype // 构造函数的prototype
const res = Constructor.apply(obj, arguments);
return typeof res === 'object' ? res : obj;
}
function A (name,age) {
this.name = name
this.age = age
}
var obj = New(A, 1, 2); 等价 var obj = new A(1, 2);
console.log(obj)四、bind的模拟实现
func.bind(thisArg[, arg1[, arg2[, ...]]]) // bind 用法Function.prototype.bind = function(thisArg) {
if(typeof this !== 'function'){
throw new TypeError(this + 'must be a function');
}
// 存储函数本身
const _this = this;
// 去除thisArg的其他参数 转成数组
const args = [...arguments].slice(1)
// 返回一个函数
const bound = function() {
// 可能返回了一个构造函数,我们可以 new F(),所以需要判断
if (this instanceof bound) {
return new _this(...args, ...arguments)
}
// apply修改this指向,把两个函数的参数合并传给thisArg函数,并执行thisArg函数,返回执行结果
return _this.apply(thisArg, args.concat(...arguments))
}
return bound
}bind 方法 会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
案列走起
Function.prototype.bind1 = function(thisArg) {
if(typeof this !== 'function'){
throw new TypeError(this + 'must be a function');
}
const _this = this;
const args = [...arguments].slice(1)
const bound = function() {
if (this instanceof bound) {
return new _this(...args, ...arguments)
}
return _this.apply(thisArg, args.concat(...arguments))
}
return bound
}
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
var bind2 = bar.bind1(foo,'name','age')
console.log(bind2())五、this 指向的技巧判定
非严格模式下 this 指向全局对象(浏览器下指向 Window,Node.js 环境是 Global ), 严格模式下,this 绑定到 undefined , 严格模式不允许this指向全局对象。
bar() => bar.call(undefined) 严格模式下bar函数的this指向undefined
bar() => window.bar.call(window) 非严格模式下bar函数的this指向window
obj.foo() => obj.foo.call(obj) 其中foo函数的ths指向obj 由例子分解开始:注意普通函数和箭头函数的指向区别
var name = 'window';
var person = {
name: 'person',
sayHi: function(){
console.log('sayHi', this.name)
},
sayTime: function() {
console.log('sayTime', this.name)
setTimeout(function(){ // this指向调用函数的对象
console.log('setTimeout', this.name)
}, 100)
},
arrow: function() {
setTimeout(() => { // this指向最近作用域
console.log('arrow', this.name)
}, 100)
}
}
person.sayHi(); // person.sayHi.call(person)
person.sayTime() // person.sayTime.call(person)
person.arrow() // person.arrow.call(person)
解剖里面的这里this指向
person.sayHi(); // person.sayHi.call(person) 所以sayHi函数里面的this指向person
console.log('sayHi', this.name) // 输出person
person.sayTime() // person.sayTime.call(person) 所以sayHi函数里面的this指向person
console.log('sayTime', this.name) // 输出person
setTimeout 和 setInterval 调用函数是 window.fn() => 所以this指向window
console.log('setTimeout', this.name) // 输出 windowperson.arrow() // person.arrow.call(person) 所以arrow函数里面的this指向person
console.log('arrow', this.name)
// 同样是window.fn() => 所以this指向window 但是但是但是注意了这是箭头函数所以这里箭头函数this指向最近层作用域arrow,arrow函数里面的this指向person,所以
console.log('arrow', this.name) // 输出person[]语法中的this
function fn() {
console.log(this.length)
}
var arr = [1, 2, fn]
arr[2]()
// 可以看做是 arr.2.call(arr) ==> fn.call(arr) ==> fn函数this指向arr
// 输出 3 或者此函数
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
console.log(this)
console.log(arguments)
fn(); // window.fn()
arguments[0](); // fn.call([fn,1])
}
};
obj.method(fn, 1) arguments[0](); ==> arguments.0.call(arguments) ==> fn.call([fn,1])箭头函数this和普通函数
箭头函数没有自己的 this,当在内部使用了 this时,它会指向最近一层作用域(函数作用域和全局作用域)内的 this。由于没有自身的this,所以只能根据作用域链往上层查找,直到找到一个绑定了this的函数作用域,并指向调用该普通函数的对象。
var name = 'hello'
var obj = {
name: 'latency',
arrow: function(){
// console.log(this) // 最近的作用域
return () => { // 箭头函数内部没有this,指向最近的作用域,会逐级向上查找
console.log('name:', this.name);
}
},
basic: function(){
// console.log(this)
return function () { // 自身有this,指向调用闭包的对象
console.log('name:', this.name);
}
}
}
obj.arrow()() // 输出latency
// 可以 看做是 var fn = obj.sayName() window.fn() // window.fn() 虽然是window调用了闭包,但是箭头函数this查找机制是按照自己来的,这是和普通的函数的区别
obj.basic()() // 输出hello箭头函数和普通函数的this指向区别
永远记住这两点OK
箭头函数: this 它会指向最近一层作用域
普通函数:this 永远指向调用它的对象总归:
我们在使用函数的调用,初衷当然都是想让this指向调用的当前函数的对象,所以什么时候用箭头函数也要留神。比如。
var name = 'cheng';
var obj = {
name: 'latency',
sayName: () => {
console.log('name:', this.name)
}
}
obj.sayName() // sayName整体是箭头函数,所有this就指向了window。所以在函数中需要使用闭包的时候,用箭头函数就很方便了。
经典题目
var name = 'window'
var person1 = {
name: 'person1',
show1: function () {
console.log(this.name)
},
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = { name: 'person2' }
person1.show1()
person1.show1.call(person2)
person1.show2()
person1.show2.call(person2)
person1.show3()()
person1.show3().call(person2)
person1.show3.call(person2)()
person1.show4()()
person1.show4().call(person2)
person1.show4.call(person2)()总算弄清楚了。很开森。。。