原型、构造函数、原型链、继承

162 阅读5分钟

使用new关键字生成一个对象内部的实现过程

   var arr = new Array()

  1. 创建新对象,即var arr = {};

  2. 将创建的新对象arr的_proto_属性指向构造函数的prototype对象;即arr._proto = Array.prototype,将构造函数的作用域赋给新对象(this指向新对象);

  3. 创建的新对象arr调用构造函数,即Array.call(arr);

  4. 返回新对象,如果构造函数返回的是基本数据类型,则返回的arr是构造函数的对象;如果构造函数返回的不是基本数据类型值(funcition,对象等),则返回的arr是该构造函数return的值 (function,对象等)

    function Person(name,age){
    this.name = name;
    this.age = age;
    this.myFn = function(){
    console.log(this.name + this.age)
    }; }

    function newFn(){
    var constructorFun = Array.prototype.shift.call(arguments);//获取构造函数Person
    var instance = Object.create(constructorFun.prototype);
    constructorFun.apply(instance, arguments);
    return instance; }

    var person = newFn(Person, 'liming','30'); person.myFn();

    // arguments 是一个类数组对象,虽然有下标,但是并非真正的数组;直接调用shift会报错 // Array.prototype.shift.call(arguments) call是用来改变函数执行时this的指向,这里以arguments对象为this来执行Array.prototype.shift函数,删除数组的第一个元素并返回

call、apply、bind的区别

call&apply

每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。首先,apply()方法接受两个参数:一个是在其中运行函数的作用域,另一个是参数数组。call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同

function sum(num1,num2) {    
    return num1 + num2;
}
function callSum1(num1,num2) {    
    return sum.apply(this,arguments);
}
function callSum2(num1,num2) {   
    return sum.apply(this,[num1,num2]);
}
function callSum3(num1,num2) {   
    return sum.call(this,num1,num2);
}

alert(callSum1(10,10));//20
alert(callSum2(10,10)); //20
alert(callSum3(10,10)); //20

//callSum1在执行sum函数时传入了this作为sum函数的this值 以及 arguments对象
//callSum1是在全局作用域下被调用,因此this是window对象

使用call、apply扩充函数运行的作用域

var str = '周日'
function fn(){      
    console.log(this.str)
}
var obj = {  str: '星期日'}
fn();//'周日'
fn.apply(window);//'周日'
fn.apply(this);//'周日'
fn.apply(obj);//'星期日'

//使用call和apply来扩充作用域最大的好处,就是对象不需要与方法有任何耦合关系

bind

bind()这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值

var str = '周日'
function fn(){   
    console.log(this.str)
}

var obj = {  str: '星期日'}
var fnBind = fn.bind(obj);
fnBind();//'星期日'
console.log(fnBind._proto_ === obj.prototype)  //true

//fn()调用bind()并传入对象obj,创建fnBind()函数
//fnBind函数的this是obj,因此即使在全局作用域中调用,也并不影响

总结:

(1)call、apply只是切换了函数内部this的调用,但是执行的方法依然是原始对象上的方法;即使你在 Array.prototype.slice.call(obj)的 obj 上 覆盖了slice 方法 ,依然会执行 Array 上的 slice 方法;

(2)由于call、apply方法改变的是 函数执行时所在的对象,会立即执行函数,因此需要把绑定语句写在函数体内;使用函数改变this指向时最好使用bind方法;bind是返回改变了上下文后的一个函数;

(3)bind方法每运行一次就会返回一个新函数,这会引发一些新问题。

 element.addEventListener('click', o.m.bind(o));

click绑定bind方法生成了一个匿名函数,这样会导致无法取消绑定;

 element.removeEventListener('click', o.m.bind(o));//这个就无效了

正确的方式是:

var listener = o.m.bind(o);
element.addEventListener('click', listener);
//  ...
element.removeEventListener('click', listener);

call&apply的实现原理

fn.apply(obj, array)实现原理

  • 将要执行的函数fn设置为对象Obj的属性

  • 执行obj.fn(args)函数

  • 不能给obj对象添加新的属性,因此要删除这个属性

  • 其中要对obj进行判断,为null和undefined时,要把this执行window

    Function.prototype.call = function (context) {
    // 首先判断是不是函数调用该方法,如果不是,则抛出异常
    if (typeof this !== "function") throw new TypeError('Error'); // 为null或undefined时,要把this指向window
    var context = context || window;
    // 将函数设置为obj的属性
    context.fn = this;
    // 类数组解构参数
    var args = [...arguments].slice(1);
    // 执行函数
    var result = context.fn(...args);
    // 删除函数
    delete context.fn;
    // 返回执行结果
    return result; } var a22 = 55; function f22(){
    console.log(this.a22);//55 } f22.call();

    //context.fn = this;因为f22.proto = Function.prototype,f22是通过Function实例化生成的对象 实例化对象 执行 构造函数的原型方法,则方法里的this指向实例化对象即f22

    //在window对象上添加了fn属性,fn属性值为f22;相当于window.fn = f22() //执行window.fn(),返回执行结果,删除window上的fn属性

bind简单实现原理

Function.prototype.myBind = function(context){
    if(type this !== "function") throw new TypeError('error');
    var me = this;
    var args = [...arguments].slice(1);
    return function (){
        return me.apply(context, args.concat(...arguments);
        // return me.apply(context, args.concat(Array.prototype.slice(args)))
    }
}

类数组对象

数组是一个特殊的object对象。js中存在一种类数组对象。比如{0:1,length:1}或者Dom对象,或者arguments对象;数组只是一种特殊的对象,数组的特殊性体现在,它的键默认是按次序排列的整数(0,1,2...),所以数组不用为每个元素指定键名,而对象的每个成员都必须指定键名。

var obj = {0:1,1:1,2:2,length:3}  //length属性很重要,有了length就可以像数组一样遍历这个对象

Array.prototype.slice.apply({0:1,length:1}); 通过apply,将slice方法中的this指向对象{0:1,length:1},遍历生成新的数组对象

常常碰到需要将arguments对象转成Array来处理的情形,就可以用这个技巧

var arg0 = Array.prototype.slice.apply(arguments); 

//apply是用来改变函数执行是this指向的,这里以argumens对象为this来执行Array.prototype.slice函数,而Array.prototype.slice函数不带参数时默认返回的是数组对象本身。

当然也可以通过类数组解构的方式,转化成数组

var arr = [...arguments]

参考链接:

blog.csdn.net/liujianfeng…

blog.csdn.net/qq_27626333…

juejin.cn/post/684490…