JS 兼容、继承、bind、this

2次阅读

共计 4451 个字符,预计需要花费 12 分钟才能阅读完成。

这一篇文章主要是讲述一些有关 js 的小知识点。因为我也不是很精通哪一方面只能把自己知道的一点点写出来。取名大杂烩也是这意思吧,既然是道菜那么就来尝尝我的手艺吧。
第一道菜
1. 首先,我想说一下事件绑定。事件绑定我们都知道有:on + ‘type’ 的事件绑定还有 addEventListener 的事件绑定。

在以前因为各种原因导致我们在不同的浏览器上面实现一样的功能出现了兼容的写法,首先我们先来看看事件绑定的兼容写法。
function bindEvent(obj, type, handle) {
if(window.addEventListener){
obj.addEventListener(type, handle, false);
}
if(window.attachEvent){// IE
obj.attachEvent(‘on’ + type, handle);
}
obj[‘on’ + type] = handle;
}

事件解除除了 on + ‘type’ 其他的就是怎么绑定怎么解除。
function delEvent(obj, type, handle) {
if(window.removeEventListener){
obj.removeEventListener(type, handle, false);
}
if(window.detachEvent){// IE
obj.attachEvent(‘on’ + type, handle);
}
obj[‘on’ + type] = null;
}

顺带提一下 IE 与其它浏览器获取 target 和 event 的兼容方式。
function handle(e){
let event = e || window.event; // IE
let target = e.target || e.srcElement;
}
下一篇我会详细介绍 DOM 事件绑定和 DOM 事件等级。

第二道菜
2. 这个知识点我们来说一下继承。

说到 js 的继承,我是学后端的在 C ++、Java 里面都跟 js 的继承不一样,所以一开始不好理解也觉得怪怪的。虽然 ES6 形式上有点像吧。那我们先来看看 js 一开始的继承。
function Father(){
this.a = 1;
}
Father.prototype.show = function () {
console.log(this.a);
}
function Son(){
Father.call(this);
}
let son = new Son();
console.log(son.a);
console.log(son.show());

我们可以发现这种是拿不到父级原型上的函数的。

我们再来看看第二种
function Father(){
this.a = 1;
}
Father.prototype.show = function () {
console.log(this.a);
}
function Son(){
Father.call(this);
}
Son.prototype = Father.prototype; // 多了这一行
let son = new Son();
console.log(son.a);
console.log(son.show());

我们发现拿到了原型上的函数,但这样就是最好的了吗?我们一起来看看。当我们查看 Son 的时候发现了一个奇怪的事。
我们可以看到 Son 的 constructor 变成了 Father,这是为什么呢?因为 constructor 是原型上的函数,我们改变了 Son 的原型,因为 Father 的 constructor 是 Father 所以 Son 的 constructor 就变成了 Father。而且这种方法我们改变 Son.prototype 时 Father.prototype 也会改变,那这说明我们的方法还是不够完美。

我们再来看看第三种
function Father(){
this.a = 1;
}
Father.prototype.show = function () {
console.log(this.a);
}
function Son(){
Father.call(this);
}
function F(){}; // 借用中间层来防止 Son 改变 Father 的原型
F.prototype = Father.prototype;
Son.prototype = new F();
Son.prototype.constructor = Son; // 改变 Son 的 constructor
let son = new Son();
console.log(son.a);
console.log(son.show());
console.log(son.constructor);

说到了构造函数那我们就看看什么是构造函数。
function Father(){
this.a = 1;
}
“` 这个是不是构造函数?如果说是那你就错了,因为你思维固定了。我们都说构造函数开头首字母大写但那只是人为的规定并不是语法。还有如果有人问你 “`this“` 是谁,你可以放肆的告诉他,你没调用我知道是谁啊。“`new“` 只是一个操作符,任何函数都能通过 new 来执行,并不只是构造函数,只不过我们认为 “`new“` 之后的都是构造函数。
+ 那你知道 “`new“` 的时候发生了什么吗?我们一般都说四步走
+ 首先创建一个新的对象。
+ 改变这个对象的 this
+ 改变这个对象的原型
+ 返回这个对象
我们试着写一写
function myNew(){
let obj = {};
let Constructor = arguments[0];
obj.__proto__ = Constructor.prototype;
Constructor.apply(obj, arguments);
return obj;
}
let a = myNew(Son);
console.log(a);
“`

补充:我们都知道 ES6 那么他的继承跟我们的继承有什么区别呢?我们来看一下 ES6 的继承。
class Father {
constructor(){
this.a = 1;
}
show(){
console.log(this.a);
}
}
class Son extends Father {
constructor(){
super();
}
}
let son = new Son();
console.log(son.a);
console.log(son.show());
我们发现是一样的。只不过是这种实现方式更加让人容易理解并且接受尤其是对于习惯了 C ++/Java 之类语言的人。其实 class 这种实现方式只是一个‘语法糖’, 当我们用 typeof Son 的时候我们发现他是一个 function, 其实它就是一个函数只不过更加语义化了,而且里面定义的方法其实是定义在了它的原型上面,我们输出一下 Father 看看。

第三道菜
3. 我们再来介绍一下 call、bind、apply

它们都是用来改变 this 的,只不过有一些小的差别。

call 和 apply 除了传参方式的不同外,没有不同的地方。
bind 返回一个函数,call、apply 立即执行。

演示我觉得应该不用了吧,因为我不是在写文档,那么我们说一些什么呢,就说说怎么模拟实现吧。我这里用的是 ES6 的语法,我只是觉得这样写比较简单但总体思路不变。下面以这个为例:
var a = 3;
var obj = {
a: 1
}
function show(g) {
console.log(this.a, g);
}
call
Function.prototype.callh = function(context){
let args = […arguments].slice(1); // 首先获取到传递的参数
context.fn = this; // 获取当前的调用者,并添加一个方法
context.fn(…args); // 传入参数
delete context.fn; // 删除新增的函数
}
show.callh(obj, 2);

apply
Function.prototype.applyh = function(context){
let args = arguments[1]; // 跟 call 一样,只不过 apply 传进来的是数组,所以 arguments[1] 指的是后面的参数
context.fn = this;
context.fn(…args);
delete context.fn;
show.applyh(obj, [2]);
}

bind(重点)
// 简易版
Function.prototype.bindh = function(context){
let args = […arguments].slice(1);
let that = this;
return function (argument) {// bind 返回一个函数
let args2 = […arguments].slice(0);
that.call(context, …args.concat(args2));
}
}
show.bindh(obj)(5);
但上面 bind 的方式有一点错误。我们来看看 js 里面的 bind 和我们的在 new 之后有什么区别。上面是 js 的后面是模拟的。看到了吧。
因为原型的原因,我们来改进一下。
Function.prototype.bindh = function(context){
let args = […arguments].slice(1);
let that = this;
function bnd(){}
let fn = function (argument) {
let args2 = […arguments].slice(0);
return that.call(this instanceof fn ? this : context, …args.concat(args2));
}
bnd.prototype = this.prototype;
fn.prototype = new bnd();
return fn;
}
这样就行了。
第四道菜
4. 再来讲一讲 this

this 一直是我们困惑的问题,有时候能看懂但代码一多,调用一多,我们就蒙了,下面我来简单介绍一下。

在默认情况下 this 指向 window(非严格模式)
谁调用指向谁
call.apply
bind
new

我想到的差不多就这几种吧,你或许会发现其实是按 this 绑定的优先级升序排序的。如果你看懂了 bind 的模拟实现也许会知道为什么 bind 的优先级会高于 call、apply。我觉得弄清楚这些 this 应该不是多大的问题吧,来一段代码看看。
var a = 3;
var obj = {
a: 1,
fn(){
console.log(this.a);
}
+
}
function show() {
console.log(this.a);
}
show(); //3
obj.fn(); //1
show.call(obj); // 1
show.apply(obj); // 1
show.bind(obj)(); // 1
show.bind(window).call(obj); //3 bind 优先级高,跟绑定顺序没区别
希望这些菜能满足您的胃口,但愿也能给您填饱一些肚子。我以后还会继续努力提高自己的厨艺,希望尝到这个菜的人都会喜欢。

正文完
 0