原型继承、判断数组、typeof、this及面试题

一、原型继承

1、原型链继承:全量继承,无法实现部分继承

缺点:继承了过多不必要的属性

// 父级
Professor.prototype = {
    name: 'Zhang',
    tSkill: 'JAVA'
}
function Professor() {}
var professor = new Professor();

// -------------分割线----------

// 子级
Teacher.prototype = professor;
function Teacher() {
    this.name = 'Wang',
    this.mSkill = 'JS'
}
var teacher = new Teacher();

// -------------分割线----------

// 孙子级
Student.prototype = teacher;
function Student() {
    this.name = 'Li',
    this.pSkill = 'HTML'
}
var student = new Student();
console.log(student);

2、借助 call/apply 实现继承: 只能借用属性和方法,无法继承原型

缺点:无法继承借用构造函数的原型

Teacher.prototype.wife = 'Liu';

function Teacher(name, mSkill) {
    this.name = name;
    this.mSkill = mSkill;
}

function Student(name, mSkill, age, major) {
    Teacher.apply(this, [name, mSkill]);
    this.age = age;
    this.major = major;
}

var student = new Student('Zhang', 'JS', 18, 'Computer');
console.log(student);

3、公共原型继承

 缺点:两个原型会相互影响,更改其中一个原型,更一个对应的原型也会被更改。

function Teacher() {
    this.name = 'Li';
    this.tSkill = 'Java';
}
Teacher.prototype = {
    pSkill: 'JS'
}
var t = new Teacher();
console.log(t);

function Student() {
    this.name = 'Wang';
}
Student.prototype = Teacher.prototype;
Student.prototype.age = 18;

var s = new Student();
console.log(s);

4、企业级继承:圣杯模式

加中间缓冲对象,即可解决只继承父级的原型,而不继承构造属性

//父构造函数
function Teacher() {
    this.name = 'Li';
    this.tSkill = 'Java';
}
Teacher.prototype = {
    pSkill: 'JS'
}
var t = new Teacher();
console.log(t);

// 中间对象缓冲
function Buffer() {}
Buffer.prototype = Teacher.prototype;
var buffer = new Buffer();
console.log(buffer);

// 子构造函数
function Student() {
    this.name = 'Wang';
}
Student.prototype = buffer;
Student.prototype.age = 18;

var s = new Student();
console.log(s);

5、圣杯封装

function inherit(Target, Origin) {
    function Buffer(){}
    Buffer.prototype = Origin.prototype;
    Target.prototype = new Buffer();
    // 还原构造器
    Target.prototype.constructor = Target;
    // 设置继承源
    Target.prototype.super_class = Origin;
}

// 使用
function Teacher() {}
Teacher.prototype.name = "Zhang";
function Student() {}
inherit(Student, Teacher);
var t = new Teacher();
var s = new Student();
console.log(s);
console.log(t);

6、企业级的模块化封装:闭包+圣杯封装

var inherit = (function() {
    var Buffer = function() {}
    return function(Target, Origin) {
        Buffer.prototype = Origin.prototype;
        Target.prototype = new Buffer();
        // 还原构造器
        Target.prototype.constructor = Target;
        // 设置继承源
        Target.prototype.super_class = Origin;
    }
})();

function Teacher() {}
Teacher.prototype.name = "Zhang";
function Student() {}
inherit(Student, Teacher);
var t = new Teacher();
var s = new Student();
console.log(s);
console.log(t);

7、企业级协同开发:一个简单的示例

var inherit = (function() {
    var Buffer = function() {}
    return function(Target, Origin) {
        Buffer.prototype = Origin.prototype;
        Target.prototype = new Buffer();
        // 还原构造器
        Target.prototype.constructor = Target;
        // 设置继承源
        Target.prototype.super_class = Origin;
    }
})();


var initPrammer = (function(){
    var Programmer = function(){}
    Programmer.prototype = {
        name: '程序员',
        tool: '计算机',
        work: '编写应用程序',
        say: function(){
            console.log(this.myName + this.name + ',' + this.tool + this.work + ',' + this.lang.toString());
        }
    }

    function FrontEnd(){}
    function BackEnd(){}

    inherit(FrontEnd, Programmer);
    inherit(BackEnd, Programmer);

    FrontEnd.prototype.lang = ['HTML', 'CSS'];
    FrontEnd.prototype.myName = '前端';
    BackEnd.prototype.lang = ['JAVA', 'PHP'];
    BackEnd.prototype.myName = '后端';

    return {
        FrontEnd: FrontEnd,
        BackEnd: BackEnd
    }
})();

var frontEnd = new initPrammer.FrontEnd();
frontEnd.say();
var backEnd = new initPrammer.BackEnd();
backEnd.say();

二、判断数组的三种方法

// 判断一个变量是否为数组
var a = [];
console.log(a.constructor); // ƒ Array() { [native code] }
console.log(a instanceof Array); // true
var str = Object.prototype.toString.call(a);
console.log(str); // [object Array]
console.log(str === '[object Array]'); // true - 常用

// 缓存处理
var str = Object.prototype.toString,
    trueTip = '[object Array]';
console.log(str.call(a) === trueTip); // true - 常用

// Object.prototype.toString.call 的原理
Object.prototype = {
    toString: function() {
        this.toString(); // call 将 this 替换为 a
    }
}

三、原型封装typeof方法

// 深度封装typeof
// 原始值:number string boolean object function undefined -> string
// 引用值:object
// null: object
function myTypeof(val) {
    var type = typeof(val);
    var toStr = Object.prototype.toString;
    var res = {
        '[object Array]': 'array',
        '[object Object]': 'object',
        '[object Number]': 'object number',
        '[object String]': 'object string',
        '[object Boolean]': 'object boolean'
    }
    if (val === null) {
        // null
        return 'null';
    } else if (type === 'object') {
        // 引用值
        var ret = toStr.call(val);
        return res[ret];
    } else {
        // 原始值
        return type;
    }
}

console.log(myTypeof(new Number(1))); // 'object number'

四、this指向

// this 总结
// 1、全局 this -> window
// 2、预编译函数 this -> window
// 3、apply/call 改变 this 指向
// 4、构造函数的 this 指向实例化对象

// 构造函数的AO
function Test() {
    // var = this {
    // __proto__: Test.prototype
    // }
    this.name = '123';
}
var test = new Test();

// AO = {
//     this: window -> {
//         name: '123',
//         __proto__: Test.prototype
//     }
// }

// GO = {
//     Test: function test(){...},
//     test: {}
// }

五、几个面试题

function foo() {
    bar.apply(null, arguments); // 传 null 就相当于没有this指向改变
    // bar.call(); -> bar() -> bar(arguments);
    // 函数执行: bar() -> bar.call() -> bar.call(arguments) -> bar(arguments);
}
function bar() {
    console.log(arguments); // 最开始为 [] -> 执行变为传入的参数
}
foo(1, 2, 3, 4, 5); // Arguments(5) [1, 2, 3, 4, 5]
// JS 的 typeof 可能返回的值有哪些?
// object(null)/boolean/number/string/undefined/function
// isNaN 函数过程
function isNaN1(num) {
    // 因为 NAN 不等于任何数, 所以需要转化为字符串再进行比较
    var res = Number(num) + '';
    if (res == 'NaN') {
        return true;
    } else {
        return false;
    }
}
console.log(isNaN1('123')); // false
console.log(isNaN1('abc')); // true
var a = '1';
function test() {
    var a = '2';
    this.a = '3';
    console.log(a);
}
test(); // '2'
new test(); // '2'
console.log(a); // '3'
var a = 5;
function test() {
    a = 0;
    console.log(a);
    console.log(this.a);
    var a;
    console.log(a);
}
test(); // 0  5  0
new test(); // 0  undefined  0
// this 指向 -很难
var name = '222';
var a = {
    name: '111',
    say: function() {
        console.log(this.name);
    }
}
var fun = a.say;
// var fun = function() {
//     console.log(this.name); // this -> window
// }
fun(); // '222' this -> window
a.say(); // '111' this -> a
var b = {
    name: '333',
    say: function(fun) {
        // fun() 函数在外面,指向 window
        fun();
        // 函数执行了
        // +function() {
        //     console.log(this.name); // this -> window
        // }();
    }
}
b.say(a.say); // '222' // this -> window
b.say = a.say;
// function(fun) {
//     fun();
// }
// function() {
//     console.log(this.name);
// }
b.say(); // '333'this -> b

JS 符号运算优先级

// 一道综合面试题
function Foo() {
    getName = function() {
        console.log(1);
    }
    return this;
}

// 借用对象自定义属性:Foo -> function -> .getName();
Foo.getName = function() {
    console.log(2);
}

Foo.prototype.getName = function() {
    console.log(3);
}

var getName = function() {
    console.log(4);
}

function getName() {
    console.log(5);
}

// Foo没执行,只是调用自定义的属性
Foo.getName(); // 2

// 预编译
// GO = {
//     getName: undefined // 变量声明
//                    : getName() {console.log(5);} // 函数声明
//                    : getName() {console.log(4);} // 运行赋值
// }
getName(); // 4

// Foo 中的 getName 没有var,变为全局声明
// 执行GO就变为getName() {console.log(1);}
Foo().getName(); // 1
getName(); // 1

// 没执行:点号的优先级高于 new,所以Foo.getName() -> 2 -> new 2 无意义 =》 2
new Foo.getName(); // 2

// 执行了:括号优先级高于点号,Foo中没有this.getName()
// 最终在原型中找到了 Foo.prototype.getName => 3
new Foo().getName(); // 3

// new Foo().getName() -> 3 -> new 3 无意义 => 3
new new Foo().getName(); // 3