这一篇文章主要是讲述一些有关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一直是我们困惑的问题,有时候能看懂但代码一多,调用一多,我们就蒙了,下面我来简单介绍一下。

    1. 在默认情况下this指向window(非严格模式)
    2. 谁调用指向谁
    3. call.apply
    4. bind
    5. 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优先级高,跟绑定顺序没区别

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


晨得
540 声望36 粉丝