this
参考链接: 嗨,你真的懂this吗?
this是什么?
this关键字总是指向函数所在的当前对象。ES6又新增链另一个类似的关键字super。指向当前对象的原型对象。
一句话解释this是什么? this总是指向调用函数的对象
this的绑定规则

寻找函数this指向的是谁?
- 判断是否通过new的方式调用,如果是,那么this就是指向新创建的对象
- 判断是否通过bind、call、apply调用,如果是,那么this就是指向bind、call、apply后的调用的第一个参数
- 判断是否是隐式调用obj.foo(),如果是,this指向obj
- 如果以上都不是,就是普通函数调用foo(),this指向window
- 如果是箭头函数,this指向包裹箭头函数的第一个普通函数中的this
绑定优先级
new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
同时,箭头函数的 this 一旦被绑定,就不会再被任何方式所改变。
默认绑定
function foo() {
console.log(this.x);
}
var x = 'test';
fn(); //test
对于这种直接调用一个函数foo()来说,就算默认绑定,this指向window
隐式绑定
function foo() {
console.log(this.x);
}
var x = 'test';
const obj = {
x: 1,
foo: foo
}
obj.foo(); //1
隐式绑定会把函数调用中的this(即此例foo函数中的this)绑定到这个上下文对象(即此例中的obj) 对于obj.foo()来说,谁调用了函数foo,this就指向谁(obj)
显式绑定
显式绑定指的是bind、call、apply这种方式调用
call,apply和bind的第一个参数,就是对应函数的this所指向的对象。call和apply的作用一样,只是传参方式不同。
function foo() {
console.log(this.x);
}
const obj = {
x: 1,
foo: foo
}
var x = 'test';
foo.call(obj); //1
foo.call(null) //test
如果第一个参数是null或者undefined,相当于默认绑定规则
new绑定
使用new来调用函数,会自动执行下面的操作:
- 创建一个新对象
- 将构造函数的作用域赋值给新对象,即this指向这个新对象
- 执行构造函数中的代码
- 返回新对象
function foo(x) {
this.x = x
}
var fn = new foo('aaa');
console.log(fn.x); //aaa
对于new的方式来说,this被永远绑定在了新对象fn上。但是前提是构造函数中没有返回对象或者是function,否则this指向这个对象或者是function
箭头函数
function foo() {
return () => {
return () => {
console.log(this)
}
}
}
console.log(foo()()())
对于箭头函数来说,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this. 因为包裹箭头函数的第一个普通函数是foo,所以此时的 this 是 window。
箭头函数使用,注意
- 函数体内的this对象,继承的是外层代码块的this。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
- 箭头函数没有自己的this,所以不能用call()、apply()、bind()这些方法去改变this的指向。
原型/构造函数/原型链
原型
在JavaScript中,每当定义一个对象时候,对象中都会包含一些预定义的属性。其中每个对象都有一个__proto__属性,这个属性指向了原型对象。 使用原型对象的好处是所有对象实例共享它包含的属性和方法

构造函数

打开构造函数,里面还有一个prototype属性,这个属性对应的值和在__proto__中的一样。
obj.__ proto __= obj. __ proto __.constructor.prototype
结论:原型的constructor属性指向构造函数,构造函数又通过prototype属性指回原型。
注意:并不是所有函数都具有这个属性,Function.prototype.bind() 就没有这个属性。
原型链
原型链主要是解决继承的问题
每个对象都拥有一个原型对象,通过__proto__指向其原型对象,并从中继承它的属性和方法,同时,原型对象也有可能有原型,这样一层一层的,最终指向null(Object.prototype.__proto __指向的是null).这种关系称为原型链。
原型/构造函数/原型链直接的关系
定义一个构造函数Foo
new一个实例对象fn
function Foo() {
console.log(1)
}
let fn = new Foo(); // 当new一个实例对象 fn就会有一个__proto__属性,指向原型对象
fn.__proto__指向原型对象
fn.__proto__.constructor = Foo
fn.__proto__.constructor.prototype = Foo.prototype
fn.__proto__ = fn.__proto__.constructor.prototype
fn.__proto__ = Foo.prototype //true
实例fn的__proto__与构造函数的prototype指向同一个对象
原型对象 等于 构造函数的prototype

深浅拷贝
我们了解到对象(引用)类型在赋值的过程中其实是复制了地址,从而会导致改变了一方其他也都被改变的情况。通常在开发中我们不希望出现这样的问题。
let a = {
age: 1
}
let b = a
a.age = 2
console.log(b.age) // 2 希望是1
let a = {
name: 'xxx',
obj: {
aa: 3
}
}
let b = a
a.obj.aa = 5
console.log(b.obj.aa) // 5 希望是3
浅拷贝
如果我们要复制对象的所有属性都不是引用类型时,就可以使用浅拷贝,实现方式就是遍历并复制,最后返回新的对象。
function shallowCopy(obj) {
var copy = {};
// 只复制可遍历的属性
for (key in obj) {
// 只复制本身拥有的属性
if (obj.hasOwnProperty(key)) {
copy[key] = obj[key];
}
}
return copy;
}
let a = {
age: 1
}
let b = shallowCopy(a)
a.age = 2
console.log(b.age) // 1
JS内部实现了浅拷贝,如Object.assign(target, source),其中第一个参数是我们最终复制的目标对象,后面的所有参数是我们的即将复制的源对象,支持对象或数组,一般调用的方式为
var newObj = Object.assign({}, originObj);
深拷贝
其实深拷贝可以拆分成 2 步,浅拷贝 + 递归,浅拷贝时判断属性值是否是对象,如果是对象就进行递归操作,两个一结合就实现了深拷贝。
简单实现
function cloneDeep(source) {
var target = {};
for(var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (typeof source[key] === 'object') { //问题一 typeof null === 'object' 传入null 会返回{} 应该返回 null
// 问题二 数组会转成对象 [1,2] 会变成 {0: 1, 1: 2}
target[key] = cloneDeep(source[key]); // 注意这里
} else {
target[key] = source[key];
}
}
}
return target;
}
let a = {
name: 'xxx',
obj: {
aa: 3
}
}
let b = cloneDeep(a)
a.obj.aa = 5
console.log(b.obj.aa) // 3
一个简单的深拷贝就完成了,但是这个实现还存在很多问题。
- 1、因为 typeof null === 'object',传入 null 时应该返回 null 而不是 {}
- 2、没有考虑数组的兼容, 传入 [1,2 ]时 应该返回 [1,2] 而不是 {0:1, 1:2}
解决以上两个问题
function cloneDeep2(source) {
if (!isObject(source)) return source; // 非对象返回自身
// if(source === null) return null
//解决数组兼容
var target = Array.isArray(source) ? [] : {};
for(var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep2(source[key]); // 注意这里
} else {
target[key] = source[key];
}
}
}
return target;
}
// 解决typeof null === 'object'
function isObject(obj) {
return typeof obj === 'object' && obj != null;
}
更全面一些
function deepClone(source) { //递归拷贝
if(source === null) return null; //null 的情况
if(source instanceof RegExp) return new RegExp(source);
if(source instanceof Date) return new Date(source);
if(typeof source !== 'object') {
//如果不是复杂数据类型,直接返回
return source;
}
//解决数组兼容
var target = Array.isArray(source) ? [] : {};
for(let key in source) {
//如果 source[key] 是复杂数据类型,递归
target[key] = deepClone(source[key]);
}
return target;
}
推荐一个网站js库,已经实现好的一些API,可以直接用 lodash.com/ 其中深拷贝API lodash.com/docs#cloneD…