【前端】代码输出题易错点汇总

不定期补充。

异步+事件循环

  • 总结
  1. 如果没有对应的resolve/reject则状态不会变化,一直处于pending状态,不进入.then/.catch里
  2. 直接打印Promise,会打印出它的状态值和参数。 形如Promise{<fullfilled>: resolve1}/{<pending>}
控制台打印形式对应状态(标准术语)描述说明
<pending>pending初始状态,未定型
<fulfilled>: 值fulfilled成功完成并有结果值
<rejected>: 错误rejected被拒绝,有错误信息
  1. Promise的状态在发生变化之后,就不会再发生变化
  2. .then 或.catch 的参数期望是函数,传入非函数则会发生值透传,即状态还是原来第一个值(即是第一个值是非函数);Promise.resolve(3) 是 Promise 对象而非函数。所以这个参数也被忽略。
  3. Promise可链式调用,由于每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用
  4. 返回任意非 promise值都会被包裹成 promise 对象,因此这里的return new Error(‘error!!!’)也被包裹成了return Promise.resolve(new Error(‘error!!!’)),因此它会被then捕获而不是catch。
  5. 坑:.then/.catch 返回值不能是 promise 本身,否则报错:死循环。
  6. 错误如果被then的第二个参数捕获,则不会被catch捕获
  7. Promise.resolve是同步代码,因为创建一个promise
  8. finally() 不会影响原始值或错误。它的返回值会被忽略,并将原来 Promise 结果继续往后传。
  9. new Promise(r =>…r(x))则r是resolve函数
  10. promise.all只要有一个失败就进入.catch,整个失败
  11. await的语句相当于放到new Promise中,下一行及之后的语句相当于放在Promise.then中。
  12. 如果async函数中抛出错误,就会终止于错误结果,不会继续向下执行。如果让错误后面的代码执行,可用catch捕获。
  13. .finally只会在promise里全执行完的时候才能收到状态
  14. .then /.nextTick 先执行.nextTick
  15. 只要throw抛出错误,就会被catch捕获,如果没有throw抛出错误,就继续执行后面的then。
  • 习题
const promise = new Promise((resolve, reject) => {
  console.log(1);
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);
//1
//2
//4

promise.then 是微任务,在所有宏任务执行完后才执行,同时需要promise内部状态发生变化,因为这里内部没有发生变化,一直处于pending状态,所以不输出3。

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
/*
promise1
1 Promise{<resolved>: resolve1}
2 Promise{<pending>}
resolve1
*/

直接打印Promise,会打印出它的状态和值。

const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);
});
console.log(4);
/*
1
2
4
timerStart
timerEnd
success
*/

Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start');
/*
start
promise1
timer1
promise2
timer2
*/

代码执行过程如下:

  1. 首先,Promise.resolve().then是一个微任务,加入微任务队列
  2. 执行timer1,它是一个宏任务,加入宏任务队列
  3. 继续执行下面的同步代码,打印出start
  4. 这样第一轮宏任务就执行完了,开始执行微任务Promise.resolve().then,打印出promise1
  5. 遇到timer2,它是一个宏任务,将其加入宏任务队列,此时宏任务队列有两个任务,分别是timer1、timer2;
  6. 这样第一轮微任务就执行完了,开始执行第二轮宏任务,首先执行定时器timer1,打印timer1;
  7. 遇到Promise.resolve().then,它是一个微任务,加入微任务队列
  8. 开始执行微任务队列中的任务,打印promise2;
  9. 最后执行宏任务timer2定时器,打印出timer2;
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
/*
1
*/

只需要记住一个原则:.then 或.catch 的参数期望是函数,传入非函数则会发生值透传。就是当它不存在。
第一个then和第二个then中传入的都不是函数,一个是数字,一个是对象,因此发生了透传,将resolve(1) 的值直接传到最后一个then里,直接打印出1。Promise.resolve(3) 是 Promise 对象而非函数。所以这个参数也被忽略。

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')//状态改变
  }, 1000)
})
const promise2 = promise1.then(() => {//注意promise2是promise1.then
  throw new Error('error!!!')
})
/*
promise1.then(...) 本身会返回新 Promise 对象,这就是 promise2。
promise2 的状态取决于 then 中回调函数的执行结果。
*/
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)
/*注意写法
promise1 Promise {<pending>}
promise2 Promise {<pending>}
promise1 Promise {<fulfilled>: "success"}
promise2 Promise {<rejected>: Error: error!!}
*/

Promise.resolve(1)
  .then(res => {
    console.log(res);
    return 2;
  })
  .catch(err => {
    return 3;
  })
  .then(res => {
    console.log(res);
  });
/*
1   
2
*/

Promise可链式调用,由于每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用
上面的输出结果之所以依次打印出1和2,是因为resolve(1)之后走的是第一个then方法,并没有进catch里,所以第二个then中的res得到的实际上是第一个then的返回值。并且return 2会被包装成resolve(2),被最后的then打印输出2。

Promise.resolve().then(() => {
  return new Error('error!!!')
}).then(res => {
  console.log("then: ", res)
}).catch(err => {
  console.log("catch: ", err)
})
/*
"then: " "Error: error!!!"
*/

返回任意非 promise值都会被包裹成 promise 对象,因此这里的return new Error(‘error!!!’)也被包裹成了return Promise.resolve(new Error(‘error!!!’)),因此它会被then捕获而不是catch。

const promise = Promise.resolve().then(() => {
  return promise;
})
promise.catch(console.err)
/*
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
*/

坑:.then/.catch 返回值不能是 promise 本身,否则造成死循环。

Promise.reject('err!!!')
  .then((res) => {
    console.log('success', res)
  }, (err) => {
    console.log('error', err)
  }).catch(err => {
    console.log('catch', err)
  })
  //error err!!!

错误如果被then的第二个参数捕获,则不会被catch捕获

Promise.resolve('1')
  .then(res => {
    console.log(res)
  })
  .finally(() => {
    console.log('finally')
  })
Promise.resolve('2')
  .finally(() => {
    console.log('finally2')
    return '我是finally2返回的值'
  })
  .then(res => {
    console.log('finally2后面的then函数', res)
  })
/* 注意一下微任务队列顺序,Promise.resolve是同步代码 因为创建一个promise
finally() 不会影响原始值或错误。它的返回值会被忽略,并将原来 Promise 结果继续往后传。
1
finally2
finally
finally2后面的then函数 2
*/

function runAsync (x) {
    const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
    return p
}

Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))
/*
r(x, console.log(x)) 相当于:先执行 console.log(x),然后执行 r(x, undefined);
因为 console.log() 的返回值是 undefined,所以 r(x, undefined) === r(x)(只传了一个参数)
r实际上就是resolve函数 相当于new Promise((resolve) => {
  setTimeout(() => resolve(x), 1000)
})

1
2
3
[1, 2, 3]
*/

function runAsync (x) {
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
  return p
}
function runReject (x) {
  const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))

  return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
       .then(res => console.log(res))
       .catch(err => console.log(err))
/*
// 1s后输出
1
3
// 2s后输出
2
Error: 2 //进入catch `Error: ${x}`并不会打印,作为rejected 的原因传入
// 4s后输出
4

promise.all只要有一个失败就进入.catch
虽然仍会在 4 秒后打印 4,但它的结果对 .then/.catch 没有任何影响,因为 Promise.all 早已失败。
*/

function runAsync(x) {
  const p = new Promise(r =>
    setTimeout(() => r(x, console.log(x)), 1000)
  );
  return p;
}
function runReject(x) {
  const p = new Promise((res, rej) =>
    setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
  );
  return p;
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log("result: ", res))
  .catch(err => console.log(err));
/* 虽然race只捕获一次,但settimeout回调函数输出还是有的
0
Error: 0
1
2
3
*/

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
async1();
console.log('start')
/*
async1 start
async2
start
async1 end

跳出async1函数后,执行同步代码start;
在一轮宏任务全部执行完之后,再来执行await后面的内容async1 end。
await的语句相当于放到new Promise中,
下一行及之后的语句相当于放在Promise.then中。
*/

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
  setTimeout(() => {
    console.log('timer1')
  }, 0)
}
async function async2() {
  setTimeout(() => {
    console.log('timer2')
  }, 0)
  console.log("async2");
}
async1();
setTimeout(() => {
  console.log('timer3')
}, 0)
console.log("start")
/*
async1 start
async2
start
async1 end
timer2
timer3 *
timer1 *
*/

代码的执行过程如下:

  1. 首先进入async1,打印出async1 start;
  2. 之后遇到async2,进入async2,遇到定时器timer2,加入宏任务队列,之后打印async2;
  3. 由于async2阻塞了后面代码的执行,所以执行后面的定时器timer3,将其加入宏任务队列,之后打印start;
  4. 然后执行async2后面的代码,打印出async1 end,遇到定时器timer1,将其加入宏任务队列;
  5. 最后,宏任务队列有三个任务,先后顺序为timer2,timer3,timer1,没有微任务,所以直接所有的宏任务按照先进先出的原则执行。
async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
/*
script start
async1 start
promise1
script end
async1中await后面的Promise是没有返回值的,
也就是它的状态始终是pending状态,
所以在await之后的内容是不会执行的,包括async1后面的 .then。
*/

async function async1 () {
  await async2();
  console.log('async1');
  return 'async1 success'
}
async function async2 () {
  return new Promise((resolve, reject) => {
    console.log('async2')
    reject('error')
  })
}
async1().then(res => console.log(res))
/*
async2
Uncaught (in promise) error
*/

如果async函数中抛出了错误,就会终止于错误结果,不会继续向下执行。
如果让错误不足之处后面的代码执行,可以使用catch来捕获。

const async1 = async () => {
  console.log('async1');
  setTimeout(() => {
    console.log('timer1')
  }, 2000)
  await new Promise(resolve => {
    console.log('promise1')
  })
  //由于Promise没有返回值,所以后面的代码不会执行;
  console.log('async1 end')
  return 'async1 success'
} 
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)//值渗透
  .then(2)
  .then(Promise.resolve(3))//还是非函数
  .catch(4)
  .then(res => console.log(res))
setTimeout(() => {
  console.log('timer2')
}, 1000)
/*
script start
async1
promise1
script end
1
timer2
timer1
*/

const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve('resolve3');
    console.log('timer1')
  }, 0)
  resolve('resolve1');//状态只能改变一次
  resolve('resolve2');
}).then(res => {
  console.log(res)  // resolve1
  setTimeout(() => {
    console.log(p1)
  }, 1000)
}).finally(res => {
  console.log('finally', res)
  //.finally只会在promise里全执行完的时候才能收到状态
})
/*
resolve1
finally  undefined (因为还没收到resolve)
timer1
Promise{<resolved>: 'resolve1'}
*/

console.log('1');
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
/*
1
7
6
8
2
4
3<-第二轮事件循环宏任务结束(输出2 4),
5<-发现有process2和then2两个微任务可以执行: 输出3 5。
9
11
10
12
.then /.nextTick 先执行.nextTick
*/

Promise.resolve().then(() => {
    console.log('1');
    throw 'Error';
}).then(() => {
    console.log('2');
}).catch(() => {
    console.log('3');
    throw 'Error';
}).then(() => {
    console.log('4');
}).catch(() => {
    console.log('5');
}).then(() => {
    console.log('6');
});
/*
只要throw抛出错误,就会被catch捕获,
如果没有throw抛出错误,就继续执行后面的then。
1 
3 
5 
6 不再抛出错误,继续执行链中的下一个 then()。
*/

this

  • 总结
  1. this默认指向全局window
  2. 箭头函数的 this 并不是当前对象 obj,而是它定义时的外部作用域的 this(即全局 this,在浏览器中就是 window)。
  3. 如果第一个参数传入的对象调用者是null或者undefined,call方法将把全局对象(浏览器上是window对象)作为this的值。要注意的是,在严格模式中,null 就是 null,undefined 就是 undefined
  4. new obj.fun() 中的 this 并不指向 obj,而是指向由 new 创建的一个新的空对象实例,它没有原对象的属性,调用原对象属性会输出 undefined。
  5. 立即执行匿名函数表达式是由window调用的,this指向window 。
  6. obj.bar(),printA在bar方法中执行,所以此时printA的this指向的是window;foo=obj.foo,foo()是在全局对象中执行的,所以其this指向的是window
  7. call/bind/apply如果没传入this或者为null/undefined,则this指向全局window
  8. new 创建一个新的空对象,执行构造函数时该对象作为 this
    如果构造函数返回一个对象,则用返回的对象替代新创建的对象作为结果
    如果构造函数没有返回对象或返回的是基本类型,则返回新创建的对象
function foo() {//这里定义的时候this是全局window
  console.log( this.a );
}

function doFoo() {
  foo();
}

var obj = {
  a: 1,
  doFoo: doFoo
};

var a = 2; 
obj.doFoo()
//2

var a = 10
var obj = {
  a: 20,
  say: () => {
    console.log(this.a)
  }
}
obj.say() 

var anotherObj = { a: 30 } 
obj.say.apply(anotherObj)
//10
//10

箭头函数时不绑定this的,它的this来自原父级所处上下文,所以首先会打印全局window中 a 的值10。
后面虽然让say方法指向另外一个对象,但是仍不能改变箭头函数特性,它的this仍然指向全局,所以依旧会输出10。
如果是普通函数,则输出20 30

function a() {
  console.log(this);
}
a.call(null);
//window对象
/*
如果第一个参数传入的对象调用者是null或者undefined,
call方法将把全局对象(浏览器上是window对象)作为this的值。
要注意的是,在严格模式中,null 就是 null,undefined 就是 undefined
*/

var obj = { 
  name: 'cuggz', 
  fun: function(){ 
     console.log(this.name); 
  } 
} 
obj.fun()     
new obj.fun() 
/*
cuggz
undefined
new obj.fun() 中的 this 并不指向 obj,而是指向由 new 创建的一个新的空对象实例,它没有 name 属性,所以输出 undefined。
*/

var obj = {
   say: function() {
     var f1 = () =>  {
       console.log("1111", this);
     }
     f1();
   },
   pro: {
     getPro:() =>  {
        console.log(this);
     }
   }
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();
/*
1111 window对象  o是在全局执行的
1111 obj对象  obj.say(),谁调用say,say 的this就指向谁
window对象
*/

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log(this.foo);  
        console.log(self.foo);  
        (function() {
            console.log(this.foo);  
            console.log(self.foo);  
        }());
    }
};
myObject.func();
//bar bar undefined bar

立即执行匿名函数表达式是由window调用的,this指向window 。立即执行匿名函数的作用域处于myObject.func的作用域中,在这个作用域找不到self变量,沿着作用域链向上查找self变量,找到了指向 myObject对象的self。

window.number = 2;
var obj = {
 number: 3,
 db1: (function(){
   console.log(this);
   this.number *= 4;
   return function(){
     console.log(this);
     this.number *= 5;
   }
 })()//立即执行
}
var db1 = obj.db1;
db1();
obj.db1();
console.log(obj.number);     // 15
console.log(window.number);  // 40
  1. 执行db1()时,this指向全局作用域,所以window.number x 4 = 8,然后执行匿名函数, 所以window.number x 5 = 40;然后返回新 function 被赋值给 obj.db1
  2. 执行obj.db1();时,this指向obj对象,执行匿名函数,所以obj.numer * 5 = 15。
var length = 10;
function fn() {
    console.log(this.length);
}
 
var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};
 
obj.method(fn, 1);
//10 2 arguments长度为2
/*
arguments[0] 是传入的第一个参数,也就是 fn 函数
这里的调用形式是 arguments[0](), this 指向调用者,也就是 arguments 对象本身
arguments 对象有一个 length 属性,代表参数个数,这里是 2
所以 this.length = arguments.length = 2
输出:2
*/

var a = 1;
function printA(){
  console.log(this.a);
}
var obj={
  a:2,
  foo:printA,
  bar:function(){
    printA();
  }
}

obj.foo(); // 2
obj.bar(); // 1
var foo = obj.foo;
foo(); // 1
/*
1. obj.foo(),foo 的this指向obj对象,所以a会输出2;
2. obj.bar(),printA在bar方法中执行,所以此时printA的this指向的是window,输出1;
3. foo(),foo是在全局对象中执行的,所以其this指向的是window,所以会输出1;
*/

var x = 3;
var y = 4;
var obj = {
    x: 1,
    y: 6,
    getX: function() {//这个函数不是匿名函数 而是getX
        var x = 5;
        return function() { //匿名函数this指向全局window
            return this.x;
        }();
    },
    getY: function() {
        var y = 7;
        return this.y;
    }
}
console.log(obj.getX()) // 3
console.log(obj.getY()) // 6

 var a = 10; 
 var obt = { 
   a: 20, 
   fn: function(){ 
     var a = 30; 
     console.log(this.a)
   } 
 }
 obt.fn();  // 20
 obt.fn.call(); // 10 没传this指向对象则为全局window
 (obt.fn)(); // 20
 /*
(obt.fn)()给表达式加了括号,而括号的作用是改变表达式的运算顺序,
而在这里加与不加括号并无影响;相当于  obt.fn(),所以会打印出 20;
*/

function a(xx){
  this.x = xx;
  return this
};
var x = a(5);
var y = a(6);

console.log(x.x)  // undefined
console.log(y.x)  // 6
/*
函数内部的this指向window对象。
所以 this.x = 5 就相当于:window.x = 5。
之后 return this,也就是说 这里的x=window 将函数内部x值覆盖。
然后执行console.log(x.x), 也就是console.log(window.x),
而window对象中没有x属性,所以会输出undefined。
*/
function foo(something){
    this.a = something
}

var obj1 = {}

var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2

var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3
/*
new 创建了一个新的空对象,执行构造函数时该对象作为 this
如果构造函数返回了一个对象,则用返回的对象替代新创建的对象作为结果
如果构造函数没有返回对象或返回的是基本类型,则返回新创建的对象

考察this绑定的优先级。记住以下结论即可:
this绑定的优先级:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。
*/

作用域/变量提升/闭包

  • 总结
  1. var声明的是局部变量,如果没有声明的var/let/const的是全局变量
  2. 如果内外作用域各自定义了一个 var a,它们就是两个不同的变量,互不影响。
  3. 在函数内部的“函数声明”和“var 变量声明”都会被提升到当前函数作用域的顶部。
(function(){
   var x = y = 1;
})();
console.log(x); // Uncaught ReferenceError: x is not defined
/*
var x = y = 1; 实际上这里是从右往左执行的,
首先执行y = 1, 因为y没有使用var声明,所以它是一个全局变量,
然后第二步是将y赋值给x,讲一个全局变量赋值给了一个局部变量,
最终,x是一个局部变量,y是一个全局变量,所以打印x是报错。
*/

var a, b
(function () {
   var a = (b = 3);//注意区分(b==3)
   console.log(a);
   console.log(b);   
})()
console.log(a);
console.log(b);
//3 3 undefined 3
//如果内外作用域各自定义了一个 var a,它们就是两个不同的变量,互不影响。

var friendName = 'World';
(function() {
  if (typeof friendName === 'undefined') {
    var friendName = 'Jack';
    console.log('Goodbye ' + friendName);
  } else {
    console.log('Hello ' + friendName);
  }
})();
//函数内有 var friendName,变量声明被提升到函数作用域顶部,但赋值不会
//Goodbye Jack
//在 JavaScript中, Function 和 var 都会被提升(变量提升)

//注意如果是
var fn2 //只提升这个
fn2() //Uncaught TypeError: fn2 is not a function
fn2 = function() {
  console.log('fn2')
}

function a() {
    var temp = 10;
    b();
}
function b() {
    console.log(temp); 
// 报错 Uncaught ReferenceError: temp is not defined
}
a();
//js中变量作用域链与定义时的环境有关,与执行时无关。
//执行环境只会改变this、传递参数、全局变量等
//temp 是 a() 函数内部的局部变量,作用域只在函数 a() 内部,b() 函数访问不到它。

function fun(n, o) {
  console.log(o)
  return {
    fun: function(m){
      return fun(m, n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);
/*
undefined  0  0  0
undefined  0  1  2
undefined  0  1  1
而a就是是fun(0)返回的那个对象。也就是说,函数fun中参数 n 的值是0,而返回的那个对象中,需要一个参数n,而这个对象的作用域中没有n,它就继续沿着作用域向上一级的作用域中寻找n,最后在函数fun中找到了n,n的值是0。
*/

f = function() {return true;};   
g = function() {return false;};   
(function() {   
   if (g() && [] == ![]) {   
      f = function f() {return false;};   
      function g() {return true;}   //这个会被函数提升并覆盖g
      //由于在匿名函数中,又重新定义了函数g,就覆盖了外部定义的变量g
   }   
})();   
/*
![] → false
[] == false → true(因为 [] 会被转换为 '',再为 0,最后和 false 相等)
*/
console.log(f());//false

原型/继承

  • 总结
  1. JavaScript 中对象属性的查找,就是顺着 __proto__(即原型链)一层层往上找的。
  2. 实例自己有的变量就用自己的,没有的采用其原型上的
  3. this 永远指向调用该方法的对象,也就是“点.前面的对象”。就算方法是在原型链上找到的,this 依然是“点(.)前面的那个对象”。
  4. new会把构造函数中的属性赋值给新创建的实例
Person                --> 是函数对象
Person.__proto__      === Function.prototype      
Person.prototype      --> 是将来 new 出来的实例的原型
Person.prototype.__proto__ === Object.prototype   

// 函数关系
Function.__proto__ === Function.prototype // 函数自己造自己(有趣)

// 对象关系
Object.__proto__ === Function.prototype   // Object 是函数构造出来的
Object.prototype.__proto__ === null       //  原型链终点

// 实例关系
实例.__proto__ === 构造函数.prototype

构造函数.prototype:将来 new 得到实例的原型
实例.__proto__:谁构造“该对象的构造函数”
function Person(name) {
    this.name = name
}
var p2 = new Person('king');
console.log(p2.__proto__.__proto__) //Object.prototype
console.log(p2.__proto__.__proto__.__proto__) // null
console.log(p2.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错
console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错

console.log(p2.prototype)//undefined p2是实例,没有prototype属性

console.log(Person.prototype)//打印出Person.prototype这个对象里所有的方法和属性
console.log(Person.prototype.constructor)//Person
console.log(Person.prototype.__proto__)// Object.prototype
console.log(Person.__proto__) //Function.prototype

console.log(Function.__proto__)//Function.prototype
console.log(Object.__proto__)//Function.prototype
console.log(Object.prototype.__proto__)//null

// a
function Foo () {
 getName = function () {//相当于this.getName
   console.log(1);
 }
 return this;
}
// b
Foo.getName = function () {
 console.log(2);
}
// c
Foo.prototype.getName = function () {
 console.log(3);
}
// d
var getName = function () {
 console.log(4);
}
// e
function getName () {
 console.log(5);
}

Foo.getName();           // 2
getName();               // 4
Foo().getName();         // 1
//执行Foo()返回 this,这个this指向window
//Foo().getName() 成为window.getName()
getName();               // 1 

new Foo.getName();       // 2
//等价于 new (Foo.getName())

new Foo().getName();     // 3
//等价于 (new Foo()).getName(),
//先new一个Foo的实例,再执行这个实例的getName方法,但是这个实例本身没有这个方法,
//所以去原型链上边找,实例.__proto__ === Foo.prototype,所以输出 3;

new new Foo().getName(); // 3
//new (new Foo().getName())

var F = function() {};//函数也是对象
Object.prototype.a = function() {
  console.log('a');
};
Function.prototype.b = function() {
  console.log('b');
}
var f = new F();
f.a();
f.b();
F.a();
F.b()
/*
a
Uncaught TypeError: f.b is not a function
a
b

f.__proto__= F.prototype 是一个对象
F.prototype.__proto__=Object.prototype

F 是个构造函数,F 是构造函数 Function 的一个实例。
因为 F instanceof  Object === true,F instanceof Function === true,
由此可以得出结论:F 是 Object 和 Function 两个的实例
*/

function Parent() {
    this.a = 1;
    this.b = [1, 2, this.a];
    this.c = { demo: 5 };
    this.show = function () {
        console.log(this.a , this.b , this.c.demo );
    }
}

function Child() {
    this.a = 2;
    this.change = function () {
        this.b.push(this.a);
        this.a = this.b.length;
        this.c.demo = this.a++;
    }
}
Child.prototype = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;
child1.show();
child2.show();
child1.change();
child2.change();
child1.show();
child2.show();
/*
实例自己有的变量就用自己的,没有的采用其原型上的
child1.show(); // 11 [1,2,1] 5
child2.show(); // 12 [1,2,1] 5
child1.show(); // 5 [1,2,1,11,12] 5
child2.show(); // 6 [1,2,1,11,12] 5
*/

后两个结果是怎么来的?
● this.b.push(this.a),由于this的动态指向特性,this.b会指向Child.prototype上的b数组,this.a会指向child1的a属性,所以Child.prototype.b变成了[1,2,1,11];
● this.a = this.b.length,这条语句中this.a和this.b的指向与上一句一致,故结果为child1.a变为4;
● this.c.demo = this.a++,由于child1自身属性并没有c这个属性,所以此处的this.c会指向Child.prototype.c,this.a值为4,为原始类型,故赋值操作时会直接赋值,Child.prototype.c.demo的结果为4,而this.a随后自增为5(4 + 1 = 5)。

child2执行change()方法, 而child2和child1均是Child类的实例,所以他们的原型链指向同一个原型对象Child.prototype,也就是同一个parent实例,所以child2.change()中所有影响到原型对象的语句都会影响child1的最终输出结果。
● this.b.push(this.a),由于this的动态指向特性,this.b会指向Child.prototype上的b数组,this.a会指向child2的a属性,所以Child.prototype.b变成了[1,2,1,11,12];
● this.a = this.b.length,这条语句中this.a和this.b的指向与上一句一致,故结果为child2.a变为5;
● this.c.demo = this.a++,由于child2自身属性并没有c这个属性,所以此处的this.c会指向Child.prototype.c,故执行结果为Child.prototype.c.demo的值变为child2.a的值5,而child2.a最终自增为6(5 + 1 = 6)。

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
    //this.subproperty = false; 是会赋值给新创建的实例
}
SubType.prototype = new SuperType();//原型继承的写法
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
var instance = new SubType();//instance当然是SubType的实例

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七灵微

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值