资讯专栏INFORMATION COLUMN

JS 兼容、继承、bind、this

YacaToy / 3141人阅读

摘要:我们都说构造函数开头首字母大写但那只是人为的规定并不是语法。只是一个操作符,任何函数都能通过来执行,并不只是构造函数,只不过我们认为之后的都是构造函数。

这一篇文章主要是讲述一些有关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优先级高,跟绑定顺序没区别

希望这些菜能满足您的胃口,但愿也能给您填饱一些肚子。我以后还会继续努力提高自己的厨艺,希望尝到这个菜的人都会喜欢。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100698.html

相关文章

  • JS之函数(1)

    摘要:前言这段时间突然发现原生好多东西都忘记了但有些东西确实很重要所以又重新再梳理一次。 showImg(https://segmentfault.com/img/bVbqqkr?w=874&h=382); 前言 这段时间突然发现JS原生好多东西都忘记了,但有些东西确实很重要,所以又重新再梳理一次。主要有函数的3种定义方法,ES5函数this指向,call与appl用法,JS常见的4种设计模...

    宋华 评论0 收藏0
  • 常见js笔试面试题(持续更新)

    摘要:相当于绕过了浏览器端,自然就不存在跨域问题。三者的区别与服务器的交互数据始终在同源的请求中携带即使不需要,即在浏览器和服务器间来回传递。而和不会自动把数据发给服务器,仅在本地保存。和虽然也有存储大小的限制,但比大得多,可以达到或更大。 本文提供最简便的解答方式,方便快速记忆,复盘,详细答案可自己再搜一下。 js基础知识 1. javascript typeof返会的数据类型有哪些 ob...

    yuxue 评论0 收藏0
  • 基本方法笔记 - 收藏集 - 掘金

    摘要:探讨判断横竖屏的最佳实现前端掘金在移动端,判断横竖屏的场景并不少见,比如根据横竖屏以不同的样式来适配,抑或是提醒用户切换为竖屏以保持良好的用户体验。 探讨判断横竖屏的最佳实现 - 前端 - 掘金在移动端,判断横竖屏的场景并不少见,比如根据横竖屏以不同的样式来适配,抑或是提醒用户切换为竖屏以保持良好的用户体验。 判断横竖屏的实现方法多种多样,本文就此来探讨下目前有哪些实现方法以及其中的优...

    maochunguang 评论0 收藏0
  • 基于Nuclear的Web组件-Todo的十一种写法

    摘要:直捣黄龙黄龙即黄龙府,辖地在今吉林一带,应该是指长春市农安县,为金人腹地。一直打到黄龙府。指捣毁敌人的巢穴。有人会说组合优于继承的。的变更会自动更新依赖的组件。可以操作对象实例,的变更会自动更新组件,属性设置方法调用。 刀耕火种 刀耕火种是新石器时代残留的农业经营方式。又称迁移农业,为原始生荒耕作制。 var TodoApp = Nuclear.create({ add: f...

    jayce 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<