✍️ Tangxt | ⏳ 2020-06-11 | 🏷️ JS 专题 |
专题课,用于汇总前边所学的内容,其中专题有:
讲完这三大专题后,就会讲浏览器的底层运行机制,如渲染机制,包括 DOM 的回流和重绘,以这个为依托,接下来会讲到 JS 的同步和异步编程、eventloop/eventqueue 等
我没有想过浏览器的底层运行机制居然是同步和异步编程等的前置知识……
事件绑定一般有两种方式:
btn.onclick = function xxx(e) {}
btn.addEventListener('click', function xxx(){}, false)
DOM 结构如下:
<button id="btn">click me</button>
btn.onclick = function xxx() {
console.log(this); //<button id="btn">click me</button>
};
如果你这样:
function xxx() {
console.log(this);
}
btn.onclick = function() {
xxx();
};
那么 this
值就是 window
了 -> 因为我们在调用 xxx
的时候是直接裸调用的,即 xxx()
这样的!
小结:
给元素的某个事件行为绑定方法,事件触发,方法执行,此时方法中的 THIS 一般都是当前元素本身(一般:表示还存有特殊情况)
不兼容 IE6、7、8:
btn.addEventListener(
"click",
function xxx() {
console.log(this); // -> btn 这个 dom 元素
},
false
);
兼容 IE6、7、8:
// <= IE8 浏览器中的 DOM2 事件绑定
btn.attachEvent("onclick", function xxx() {
console.log(this); // -> window
});
而这就是所谓的「特殊情况」,一般我们都认为 this 是 btn
,但在 DOM2 姿势里边,为了兼容 IE6、7、8,所以可能会用到 attachEvent
这个 API,而该 API 不同于 addEventListener
,即当事件触发,执行 callback,而 callback 里边的 this
指向是 window
,而不是 btn
当然,就目前而言基本就不需要兼容 IE 了,所以这种特殊情况,基本可以无视之……
对了,还有一种特殊情况:
function xxx() {
console.log(this); // -> window
}
btn.onclick = xxx.bind(window);
其中原理:
xxx.bind(window)
首先会返回一个匿名函数 (AM), 把 AM 绑定给事件;点击触发执行 AM,AM 中的 THIS 是元素,但是会在 AM 中执行xxx
,xxx
中的 THIS 是预先指定的 WINDOW
简单来说,bind 是这样改变 this 指向的:
function bind(thisArg) {
let _this = this
return function anonymous() {
_this.call(thisArg)
}
}
规律:
THIS2:普通函数执行,它里面的 THIS 是谁,取决于方法执行前面是否有“点”,有的话,“点”前面是谁 THIS 就是谁,没有则 THIS 指向 WINDOW(当然,严格模式下是 UNDEFINED)
测试:
function xxx() {
console.log(this);
}
let obj = {
name: "frank",
xxx: xxx,
};
xxx(); // -> window
obj.xxx(); // -> obj
加深理解:
我们知道 hasOwnProperty
的定义是这样的:
Object.prototype.hasOwnProperty = function hasOwnProperty() {}
当我们这样做:
console.log(obj.hasOwnProperty("name"));
console.log(obj.__proto__.hasOwnProperty("name"));
那么结果:
hasOwnProperty方法中的 this 为 obj -> TRUE
hasOwnProperty方法中的 this 为 obj.__proto__(Object.prototype) -> FALSE
Object.prototype
旗下可没有name
这个属性!
当然,如果你这样做:
Object.prototype.hasOwnProperty.call(obj, "name")
那么这等价于:
obj.hasOwnProperty('name')
补充一点:
像那种匿名的自执行函数,其 this
一般是 window
Q: `hasOwnProperty` 和 `in` 的用法?
hasOwnProperty
:用来检测某个属性名是否属于当前对象的私有属性in
:是用来检测是否为其属性(不论私有还是公有)这里的私有和公有是针对于是否可以访问原型链上的方法来说的……
console.log(obj.hasOwnProperty("name")); // =>true
console.log(obj.hasOwnProperty("toString")); // =>false
console.log("toString" in obj); // =>true
规律:
THIS3:构造函数执行(
new xxx
),函数中的this是当前类的实例
测试:
function Fn(name) {
console.log(this);
// => this.xxx = xxx 是给当前实例设置私有属性
this.name = name || 'hi';
}
let f = new Fn("frank"); // -> this -> f实例
let x = new Fn; // -> this -> x实例
一些格式化工具,会把
let x = new Fn
写法格式化成let x = new Fn()
规律:
THIS4:箭头函数中没有自身的THIS,所用到的THIS都是箭头函数作用域的上一级作用域中的THIS -> 或者说是定义箭头函数的上下文中的THIS
测试:
let xx = {
name: "xx",
fn: () => {
// 该箭头函数作用域的上一级就是全局作用域!
console.log(this); // -> window
},
};
xx.fn();
简单来说,这个this就跟普通变量一样,当前作用域找不到,就顺着作用域链往上级找……
当然,箭头函数没有的东西还有很多:
prototype
(也就是没有构造器),所以它不能被new执行如:
let xx = {
name: "xx",
fn: (...args) => {
console.log(args); // -> [1,2,3]
console.log(this, arguments); // 运行时错误:arguments is not defined
},
fx: function() {
console.log(this, arguments);
},
};
xx.fx(4, 5, 6);
xx.fn(1, 2, 3);
箭头函数的一些场景应用:
场景一:
有这么一段代码:
let obj = {
name: "xx",
fn: function() {
console.log(this); // -> xx实例
return function yy() {
console.log(this); // -> window
this.name = "obj";
};
},
};
let x = obj.fn();
x();
在我们知道x所在的上下文中的 this
是 obj
的情况,我们想让 x
执行的时候,改变 obj
的name属性值为 obj
?
简单来说,有两种做法:
①自己声明一个变量,如 _this
,然后把 x
里边的 this
改成是 _this
let obj = {
name: "xx",
fn: function() {
console.log(this); // -> xx实例
let _this = this;
return function yy() {
console.log(this); // -> window
console.log(_this); // -> xx实例
_this.name = "obj";
};
},
};
let x = obj.fn();
x();
②使用箭头函数
let obj = {
name: "xx",
fn: function() {
console.log(this); // -> xx实例
return () => {
console.log(this); // -> xx实例
this.name = "obj";
};
},
};
let x = obj.fn();
x();
场景二:
有这么一段代码,我们想让一个定时器在1s后改变 obj
的 name
属性值:
let obj = {
name: "xx",
fn: function() {
console.log(this) // -> obj
setTimeout(function() {
console.log(this); // -> window
this.name = "obj";
}, 1000);
},
};
obj.fn();
定时器的callback中的 this
是 window
,这一点就像是这样:
window.setTimeout(function() {
console.log(this); // -> window
}, 1000);
btn.addEventListener(
"click",
function() {
console.log(this); // -> btn
},
false
);
我们要让定时器中的 callback
中的 this
为 obj
,那么只需要让 callback
为箭头函数就行了:
let obj = {
name: "xx",
fn: function() {
console.log(this); // -> obj
setTimeout(() => {
console.log(this); // -> obj
this.name = "obj";
}, 1000);
},
};
obj.fn();
为啥会这样呢?因为JS是静态作用域的,而箭头函数是没有预设的this
变量的,所有箭头函数在哪儿定义的,那么其里边的this
变量就会顺着作用域链往哪儿找……
小结:
new
执行,而且也不考虑this
的话,那么我们一般都会用「箭头函数」(存粹跑一段代码执行),当然,如果考虑this
的话,那么「箭头函数」就不能乱用了,得具体情况具体分析……this
的指向,请分情况讨论,如在「事件绑定」情况下、在「普通函数执行」情况下、在「构造函数执行」情况下,在「箭头函数执行」情况下