关于this指向问题
一.new的实现原理是什么?
在js中,只要new一个函数,就会new一个对象。
对于new创建的对象
var arr =new Array()
new的实现原理(new的过程 4步骤)
- 在内存中创建一个空对象;
- 将构造函数的作用域给新对象(this指向这个新对象)
- 执行构造函数中的代码(为这个新对象添加看属性)
- 如果构造函数中没有返回其他对象,则返回this,即创建的这个新对象;否则,返回构造函数中返回的对象。
手写实现new操作符
// new的实现原理
function _new(Con,...args){ // Con: 接收的是一个构造函数,args:是传入构造函数的参数
let obj={}; //创建空对象
obj._proto_=Con.prototype; //设置空对象的原型 obj是Con的实例
let result=Con.apply(obj,args); //绑定this,将属性和方法添加到空对象上
return result instanceof Object ? result :obj; // 如果这个函数没有返回其他对象,则返回创建的新对象obj;
}
// 构造函数Con
function Test(name,age){
this.name=name;
this.age=age;
}
Test.prototype.sayName=function(){
console.log(this.name);
}
// 实现一个new操作符
const a=_new(Test,'ZYW','20')
console.log(a.age);
二、this的指向
this的指向是在函数被调用的时候确定的。也就是在执行上下文被创建时确定的。
因此,在同一个函数中,由于调用方式的不同,this指向不一样的对象。
除此之外,在函数执行过程中,this一旦被确定,就不可能再更改了。
1.this的4种指向
(1)函数调用模式:直接调用的函数,this被绑定为全局对象。在浏览器环境下是window对象,在node环境下是空对象{}
(2)方法调用模式:对象调用,this指向该对象。(就是当函数被存为一个对象属性时,被称为这个对象的方法)
(3)构造函数调用模式:通过new的方式进行绑定,this指向这个新创建的对象。a.当new返回的是一个对象时,this就会指向返回的这个新对象, b. 当new返回的不是一个对象时,this还是指向函数的实例。
(4)箭头函数中的this:箭头函数没有自己的this,在调用箭头函数的时候,不会隐式的调用this参数,而是从定义的函数定义上下文。
补充:可以通过调用函数的apply call bind来改变this的指向。
2.结论
在一个函数上下文中,this由调用者提供,由调用方式决定。如果要准确确定this的指向,找到函数的调用者以及区分他是否独立调用十分关键。
- 如果函数独立调用,那么该函数内部的this,指向undefined。但是在非严格模式下,当this指向undefined时,它会被自动指向全局对象。
// 为了能够准确判断,我们在函数内部使用严格模式,
//因为非严格模式会自动指向全局
function fn() {
'use strict';
console.log(this);
}
fn(); // undefined 因为fn是调用者,独立调用
window.fn(); //Window fn是调用者,被window所拥有
- 如果调用者函数,被某一个对象所拥有,该函数在调用时,内部的this指向该对象;
var a = 20;
function fn() {
function foo() {
console.log(this.a);
}
foo();
}
fn(); //20
- 箭头函数的this情况
let obj = {
age: 20,
info: function() {
return () => {
console.log(this.age); //this继承的是外层上下文绑定的this
}
}
}
let person = {age: 28};
let info = obj.info();
info(); //20
let info2 = obj.info.call(person);
info2(); //28
- 练习
//1)
var a = 20;
var obj = {
a: 10,
c: this.a + 20, //由于并没有作用域限制,这里的this.a它依然处在全局作用域中,this指向为window对象。
fn: function () {
return this.a;
}
}
console.log(obj.c); //40
console.log(obj.fn()); //10
//2)
var a = 20;
var foo = {
a: 10,
getA: function () {
return this.a;
}
}
console.log(foo.getA()); // 10
var test = foo.getA;
console.log(test()); // 20
//3)
var a = 20;
function getA() {
return this.a;
}
var foo = {
a: 10,
getA: getA
}
console.log(foo.getA()); // 10
//4)
function foo() {
console.log(this.a)
}
function active(fn) {
fn(); // 真实调用者,为独立调用
}
var a = 20;
var obj = {
a: 10,
getA: foo
}
active(obj.getA); //20
3.使用call,apply显示指定this 【归结为显示绑定】
JavaScript内部提供了一种机制,让我们可以自行手动设置this的指向。它们就是call与apply。所有的函数都具有这两个方法。它们除了参数略有不同之外,其功能完全一样。
- 第一个参数都为this将要指向的对象。、
- 后面的参数 都是向将要执行的函数传递参数。
区别:call 是以一个一个的形式传递,apply是以数组的形式传递。
//1.改变this指向
function fn() {
console.log(this.a);
}
var obj = {
a: 20
}
fn.call(obj); //20 将fn的内部this绑定为obj
//2.call与apply不同的传递参数的形式
function fn(num1, num2) {
console.log(this.a + num1 + num2);
}
var obj = {
a: 20
}
fn.call(obj, 100, 10); // 130
fn.apply(obj, [20, 10]); // 50
三、 如何改变this的指向?
通过调用函数的call,apply,bind 三种方法来改变this的指向。
三者的不同点:
传参方面:
- call的传递是单个的传递(数组也可以);
- apply的传递参数是数组形式,传递单个会报错;
- bind没有规定,传递值和数组都可以。
执行方面:
- call和apply函数的执行是直接执行的;
- bind函数会返回一个函数,然后当调用这个函数的时候,才会执行。
注意:当call,apply或者bind传入的第一个参数是undefined / null时,严格模式下会抛出错误。非严格模式下,应用的是默认绑定规则(即:this指向全局对象(浏览器环境为window,node环境为global))
function info(){
//node环境中:非严格模式 global,严格模式为null
//浏览器环境中:非严格模式 window,严格模式为null
console.log(this);
console.log(this.age);
}
var person = {
age: 20,
info
}
var age = 28;
var info = person.info;
//严格模式抛出错误;
//非严格模式,node下输出undefined(因为全局的age不会挂在 global 上)
//非严格模式。浏览器环境下输出 28(因为全局的age会挂在 window 上)
info.call(null);
手写call、apply、bind
手写 call
// 手写call
// this为调用的函数
// context是参数对象
Function.prototype.myCall=function(context){
if(typeof this!=='function'){ //判断调用者是否为函数
throw new TypeError('Error');
}
context=context||window // 不传默认参数为window
context.fn=this // 新增fn属性,将值设置为需要调用的函数
const args=Array.from(arguments).slice(1); // 将arguments转化为数组,将call的参数提取出来 [...]
const result=context.fn(...args); //传递调用函数
delete context.fn; // 删除函数
return result; // 返回执行结果
}
// 普通函数
function print(age){
console.log(this.name+''+age);
}
// 自定义对象
var obj={
name:'zyw',
}
//调用函数的call方法
print.myCall(obj,1,2,3);
手写 apply
// 手写apply
Function.prototype.myApply=function(context){ // context为可选参数,如果不传的话,默认为上下文window
if(typeof this !=='function'){ //判断调用者是否为函数
throw new TypeError('Error')
}
context=context||window //不传参数默认为window
context.fn=this // 新增fn属性,将值设置为需要调用的函数
let result; //返回执行结果
if(arguments[1]){ // 判断是否有参数传入
result=context.fn(...arguments[1])
}else{
result=context.fn()
}
delete context.fn; //删除函数
return result; // 返回执行结果
}
// 普通函数
function print(age,age2,age3){
console.log(this.name+''+age+''+age2+''+age3);
}
// 自定义对象
var obj={
name:'zyw',
}
//调用函数的call方法
print.myCall(obj,[1,2,3])
手写 bind
// 手写bind
Function.prototype.myBind=function(context){
if(typeof this!=='function'){ //判断调用者是否为函数
throw new TypeError('Error')
}
const args=Array.from(arguments).slice(1); //截取获取的参数
const _this=this; //调用函数的指向
return function F(){ //返回一个函数
//因为返回了一个函数,我们可以用new F(),所以需要判断
// 对于new的情况来说,不会被任何方式改变this
if(this instanceof F){
return new _this(...arg,...arguments)
}else{
return _this.apply(context,args.concat(...arguments))
}
}
}
// 普通函数
function print( ){
console.log(this.name);
}
// 自定义对象
var obj={
name:'zyw',
}
//调用函数的bind方法
let F=print.myBind(obj,[1,2,3])
let obj1=new F();
console.log(obj1);