1.讲讲js作用域和作用域链。
js作用域有全局作用域和函数作用域。es6后增加了块级作用域。作用域定义了函数或变量的可访问问性。
多个作用域嵌套在一起(例如函数的嵌套),组成作用域链,当前作用域中找不到变量,则向外层的作用域查找,直到找到全局。
1)全局作用域就是直接在全局下定义的,一般为window或者node环境的globe。
2)函数作用域,在函数里面定义的变量只能在函数中访问(闭包先不考虑)。在函数中没找到当前变量。
3)块级作用域,直接在括号里面,通过let或者const创建的变量,只在当前的块级里面有效,且没有变量提升。
// 下面试试看 注:需要先理解var变量和函数的提升,以及let和const的暂时性死区。
var a = 'outer';
function b() {
console.log(a);
var a = 'inner';
console.log(a);
}
b();
上面的代码首先在全局定义了一个a为outer,然后定义了一个方法b为inner,b里面同样定义了一个a,同时打印出a的值。最后执行b方法;
根据作用域原则,首先最外层为一个全局作用域,里面有一个a值,在他之上有一个b的函数作用域,这个作用域里面也定义了a值为inner。b函数执行打印a值,首先在b的作用域里面找a值 第一次因为变量提升能找到a,但是取不到值,因为变量提升只提升值,所以打印应该为undefined,第二次打印还是取到了函数里面的a,此时a已经赋值了,所以为inner。
// 作用域示例图,
因此最终打印的结果为,undefined(因为此时b内部的a未赋值)和inner(赋值了)
// 如果上面代码中b里面没有新建变量a,而是直接赋值,即
var a = 'outer';
function b() {
console.log(a);
a = 'inner';
console.log(a);
}
b();
此时b的函数作用作用域中没有a值,b函数中对a的操作全部会根据原型链取到window下的a值,因此最终会打印outer和inner,并且全局的a已经变为了inner。
// 下面看看块级作用域
var a = 'outer';
{
// 暂时性死区会报错
// console.log(a,window.a)
let a = 'block';
console.log(a, window.a);
}
let和const定义的变量具有块级作用域,因此上面的代码会打印block outer。 如果将let改为var,则操作的都是全局的a,因此打印block block。
2.js原型链
首先看看官方的回答:
Javascript 规定,每一个函数都有一个 prototype 对象属性,指向另一个对象。prototype的所有属性和方法,都会被构造函数的实例继承。而prototype 就是调用构造函数所创建的那个实例对象的原型(proto)。
原型链是指有限个原型对象通过__proto__属性连接而成的实现继承与属性共享的对象链。
再来解析看: 每个函数都有一个prototype对象属性,这里的函数指的是构造函数,构造函数的根是Function。指向另一个对象,这个对象指的是自己的原型。prototype的所有属性和方法,都会被构造函数的实例继承。而prototype 就是调用构造函数所创建的那个实例对象的原型(proto)。这句话的意思是说new出来的实例的__proto__指向他的原型。到这里就说清楚了实例、构造函数和原型的关系。即上图的上面部分
原型链是指有限个原型对象通过__proto__属性连接而成的实现继承与属性共享的对象链。这句话是说在上面的基础上,每个原型的__proto__又指向自己上一级的原型,最终指向Object的prototype,再向上就是终点null。即形成了一条原型链。
最后看一个问题:自己实现一个instanceof功能?
function myInstanceof(object, constructor) {
if (typeof object !== 'object' || object === null) return false;
while (object.__proto__) {
if (constructor.prototype === object.__proto__) return true;
object = object.__proto__;
}
return false;
}
3.this指向理解
首先看一段代码
var a = 'window';
function b() {
console.log(this.a);
}
var c = {
a: 'I am c',
f: b,
}
b();
c.f();
这段代码,通过两种方式运行了b方法,但是结果却不一致,结果依次为window和I am c。 原因就是两者在执行时this的指向不一致。第一个在全局下执行,this为window,第二次在c下执行,this为c。 那么js中this的指向到底是怎么样的呢??
直接看结论(全局为window为例):
1)当作为函数直接调用时,this指向window。
2)当作为对象的方法调用时,this指向那个对象。
这两个通过开头的例子就可以说明了。
3)当作为构造函数是,this指向构造出来的对象。
function parent(name) {
console.log(this);
this.name = name;
function getname() {
console.log(this,this.name)
}
}
new parent('www')
4)用call、apply、bind绑定this是。this为绑定的那个值。
function parent(name) {
console.log(this);
this.name = name;
this.getName = function() {
console.log(this,this.name)
}
this.setName = function(name) {
this.name = name;
}
}
var p = new parent();
var obj = {};
p.setName.call(obj,'aaa');
console.log(obj);
5)箭头函数的this是定义时外层的this。
var obj = {
a: 1,
b: function() {
console.log(this.a);
}
}
obj.b();
上面函数在obj上调用,this为obj因此输出1.
如果将b改为箭头函数:
var obj = {
a: 1,
b: ()=> {
console.log(this.a);
}
}
obj.b();
箭头函数的this外层的this,外层obj在window上,因此this为window,this.a没有输出为undefined。
4.前面提到的call、apply、bind的区别,自己实现一下?
三者都能改变this的指向,区别是传参的不同,apply第二个参数为数组,所有要传递的参数都需要在数组里面,而call的参数需要一个一个传递,依次从第二个参数开始。bind在call的基础上又可以多次传参。
apply和call使用后会立即执行,而bind会返回绑定后的函数。
Function.prototype.myApply = function (context = window) {
context.fn = this;
var res;
if (arguments[1]) {
res = context.fn(...arguments[1]);
} else {
res = context.fn();
}
delete context.fn;
return res;
}
Function.prototype.myCall = function (context = window) {
context.fn = this;
var args = [...arguments].slice(1);
var res = context.fn(...args);
delete context.fn;
return res;
}
Function.prototype.myBind = function (context) {
var self = this;
var args = [...arguments].slice(1);
return function F() {
if(this instanceof F) {
return self(...args, ...arguments);
}
return self.apply(context, args.concat(arguments))
}
}
5.闭包的理解
在函数的外部是无法访问函数内部的变量的。为了解决这一问题,就引入了闭包,即在函数a的内部定义函数b,再将b返回给外部的变量c,c就能在外部访问函数a内部的变量。
闭包的缺点:变量存在引用不会即使被销毁。存在内存泄漏的问题。
最后看一个经典的输出i的问题:将下面的代码改造下,使其输出01234。
function a() {
for(var i=0;i<5;i++) {
setTimeout(function() {
console.log(i)
},1000)
}
}
几种方案
// let 块级作用域实现
function a() {
for(let i =0;i<5;i++) {
setTimeout(function() {
console.log(i)
},1000)
}
}
// 闭包实现
function a() {
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j)
}, 1000)
})(i);
}
}
// settimeout第三个参数
function a() {
for(var i=0;i<5;i++) {i
setTimeout(function(i) {
console.log(i)
},1000, i)
}
}
// 函数拆分,单独引用
function a() {
for (var i = 0; i < 5; i++) {
timer(i);
}
}
function timer(i) {
setTimeout(function() {
console.log(i);
}, 1000);
}