2018-10-22面向对象

[参考文章1]https://www.cnblogs.com/linququ/p/8733079.html
[参考文件2]https://blog.csdn.net/xuexiaodong009/article/details/37693345
[参考文章3]https://www.jianshu.com/p/1488be295383
[参考文章4]https://www.cnblogs.com/humin/p/4556820.html
[参考文章5]https://www.jianshu.com/p/15ac7393bc1f


tag: 面向对象, 原型, 原型链,继承, 变量类型判断, 变量属性


1、创建对象的方法

1.1 对象直接量

var student={name:'Tom', age:11}

直接量:花括号包裹键值对,冒号分隔键值,两个键值对之间使用逗号分开
键名:标识符、字符串直接量(包含空字符)、Symbol值
值:任意js表达式(原始或对象)

var book = {
  "main book": "helloJS", //有空格的键必须使用字符串
  "sub-title": "helloNode", //有连接符必须使用字符串
  "for": "hello" //"for"属性名字里含有保留字,必须使用字符串
};

通过对象直接量创建的对象都具有相同的原型,Object.prototype
可以使用对象名.__proto__查看对象原型

1.2 Object.create()

  1. ES5中定义的方法
Object.create(proto[,propertiesObject])
  • 第一个参数是新创建对象的原型,任意的对象(包括原型对象)
  • 第二个参数是定义对象的属性,可选
var o =Object.create({name:'x',age:11})
//o继承了原型的属性name和age
o.__proto__ //{name:'x',age:11}
  1. null为原型
var n = Object.create(null);
//n不继承任何属性或方法,包括toString()方法
  1. 创建空对象,同{}new Object(),继承Object的原型即可
var o =Object.create(Object.prototype)

同理Date、Array等对象可以通过其原型创建新对象

  1. 定义属性


    定义属性
let o = Object.create(Object.prototype, {
  // foo会成为所创建对象的数据属性
  foo: { 
    writable:true,
    configurable:true,
    value: "hello" 
  },
  // bar会成为所创建对象的访问器属性
  bar: {
    configurable: false,
    get: function() { return 10 },
    set: function(value) {
      console.log("Setting `o.bar` to", value);
    }
  }
});

扩展 定义属性Object.defineProperties,Object.defineProperty,

Object.defineProperty(obj, 'a', {
            value: true,
            writable: true,
            configurable: true,
            enumerable: true,
            getter: () => {},
            setter: () => {}
    })
  Object.defineProperties(obj, {
        'property1': {
            value: true,
            writable: true,
        },
        'property2': {
            value: true,
            writable: true,
        }
    })

1.3 工厂模式

var createPerson=function(name ,age){
    var obj=new Object; //es5的方法
    obj.name=name;
    obj.age=age;
    obj.getName=function(){
        return this.name
    }
  return obj 
}
var person=createPerson('tom',22)

缺点:

  1. 对象和实例之间没有联系
  2. 每个实例都有创建相同的方法造成资源浪费

1.4 构造函数模式

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayHi = function() {
    console.log(`My name is ${name}, i'm ${age} years old`);
  };
}
var p = new Person("Tom", 11);

解决了对象和实例之间的关系问题
缺点:资源浪费

new执行的过程(做了啥)
1、声明一个中间对象;
2、将中间对象的原型指向构造函数的原型;
3、将构造函数的this指向中间对象;
4、执行构造函数,为中间对象添加对象或方法,如果构造函数有返回值直接返回,结束执行;
5、返回该中间对象即实例对象。

new关键字的简单实现

function New(func) {
  //声明一个中间对象,该对象为返回的实例对象
  var res = {};
  if (func.prototype !== null) {
    //将实例对象的原型指向构造函数的原型
    res.__proto__ = func.prototype;
  }
  //ret为构造函数的执行结果,通过apply将构造函数内部的this指向修改为res,即实例对象,并添加对象和方法
  var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
  if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
    return ret;//构造函数有返回值
  }
  return res;//构造函数无返回值直接返回中间对象
}
var Person = function(name, age) {
    this.name = name;
    this.age = age;
    this.getName = function() {
        return this.name;
    }
}
// 通过new声明创建实例,这里的p1,实际接收的正是new中返回的res
var p1 = New(Person, 'tom', 20);
console.log(p1.getName());

js一些内置对象提供了构造器,可以用new关键字创建新对象

var o = new Object();
var a = new Array();
var d = new Date();
var r = new RegExp("js");

新对象原型就是构造函数的prototype属性,a的原型是Array.prototype

a.__proto__ === Array.prototype //true

1.5 原型模式

var Person = function() {};
Person.prototype.name = "Lin";
Person.prototype.age = 11;
Person.prototype.getName = function() {
  return this.name;
};
var p1 = new Person();
var p2 = new Person();
console.log(p1.getName === p2.getName); //true
  • 构造函数通过prototype指向原型对象
  • 构造函数生成实例对象通过proto指向原型对象
  • 原型对象通过constructor指向构造函数
    优点:减少了代码重复
    缺点:构造函数调用时无法传参

1.6 构造函数和原型组合模式

var Person = function(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.friends = ["Shelby", "Court"];
};
Person.prototype = {
  sayName: function() {
    console.log(this.name);
  }
};
var p1 = new Person("Tom", 20, "student");
var p2 = new Person("Jerry", 21, "student");
p1.friends.push("Van"); 
console.log(p1.friends); //["Shelby", "Court", "Van"]
console.log(p2.friends); //["Shelby", "Court"]
console.log(p1.friends === p2.friends); //false
console.log(p1.sayName === p1.sayName); //true

函数用于定义实例属性,原型模式用来定义方法和共享属性。
每个实例可传递参数创建自有的属性,又共享共有的属性和方法,最大限度节省内存。

2、对象的属性

2.1 如何访问对象的属性

var person = {
    name: 'TOM',
    age: '20',
    getName: function() {
        return this.name
    }
}
person.name
// 或者
person['name']

当属性名是个变量时,一定要使用第二种

let name ='age'
person.name//'TOM'
person[name]; //20

同理在创建对象的时候,也可使用[name],创建key不确定的属性

let name ='age'
var person = {
    name: 'TOM',
    [name]: '20',  
}
person.name//'TOM'
person[name]; //20

另一个应用,遍历对象

Object.keys(person).forEach(key=>console.log(person[key]))
// TOM
// 20

2.2 判断对象是否包含某个属性

通过in来判断,一个对象是否拥有某一个属性/方法,无论是该属性/方法存在于实例对象还是原型对象。

function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.getName = function() {
    return this.name;
}

var p1 = new Person('tim', 10);

console.log('name' in p1); // true
console.log('getName' in p1); // true

判断当前页面是否在移动端打开

isMobile = 'ontouchstart' in document;

2.3.属性类型

1)在使用Object.defineProperty、Object.defineProperties 或 Object.create 函数的情况下添加数据属性,writable、enumerable和configurable默认值为false。
2)使用对象直接量创建的属性,writable、enumerable和configurable特性默认为true。

  • 数据属性

    • value:属性的值,默认为undefined
    • writable:是否为只读属性。为false时,value的值不能改变
    • enumerable:属性是否可被(for...in)遍历
    • configurable:表示是否可以删除该属性。为false时,writable和enumerable属性不能被改变,该属性也不能被删除
  • 访问器属性

    • enumerable:属性是否可被(for...in)遍历
    • configurable:表示是否可以删除该属性。为false时,writable和enumerable 属性不能被改变,该属性也不能被删除
    • get: 当我们通过person.name访问name的值时,get将被调用。该方法可以自定义返回的具体值时多少。get默认值为undefined
    • set: 当我们通过person.name = 'Jake'设置name的值时,set方法将被调用。该方法可以自定义设置值的具体方式。set默认值为undefined

注意不能同时设置value,writable,get,set的值(就是不能同时拥有数据属性和访问器属性)
可以通过Object.defineProperty来修改这些属性类型
configurable

var person={
  name:'tom'
}
delete person.name
//true 删除成功
//通过Object.defineProperty设置为person设置name属性
Object.defineProperty(person,'name',{
  value:'Jake'
})
delete person.name
//false,删除失败
person.name='jade'
person.name //'Jake'修改失败

enumerable

let  person={
    name:'tom',
    age:12
}
for(var key in person){
    console.log(key) //'name'  'age'
}

//Object.defineProperty添加的新属性默认enumerable为false,不可遍历
Object.defineProperty(person,'address',{
    value:'Lodon'
})
for(var key in person){
    console.log(key) //'name'  'age'
}

//修改name属性不可遍历
Object.defineProperty(person,'name',{
   enumerable:false
})-
for(var key in person){
    console.log(key) // 'age'
}

writable

let person={
    name:'TOM'
}
person.name='Jake'
person.name   //'Jake'修改成功
//设置name属性不能修改
Object.defineProperty(person,'name',{
  writable:false
})
person.name='Tom'
person.name //Jake 修改失败

value

var  person={}
Object.defineProperty(person,'name',{
    value:'Tom'
})
person.name //'Tom'

get/set

var person={}
//通过get、set自定义访问和设置name属性的方式
Object.defineProperty(person,'name',{
      get:function(){
          //始终返回TOM
          return 'TOM'
      },
      set:function(value){
          //设置name属性时返回该字符串的新值,value为新增
          console.log(value+' in set')
      }
})
console.log(person.name) //调用get方法,一直返回TOM
person.name='Alex'//调用set方法 'Alex in set'
console.log(person.name)//还是返回TOM

请尽量同时设置get和set。如果仅仅设置get我们无法设置该属性的值,如果仅仅设置set属性无法获取属性的值
同时设置多个属性的值,可以使用Object.defineProperties

var person={}
Object.defineProperties(person,{
    name:{
         value:'TOM',
         configurable:true
    },
    age:{
        get:function(){
          return this.value||12
        },
        set:function(value){
            this.value=value
        }
    }
 })
person.name//"TOM"
person.age //22

读取属性的特性值
Object.getOwnPropertyDescriptor方法读取某一个属性的特性值

var person={}
Object.defineProperty(person,'name',{
    value:'Alex'
})
var descriptor =Object.getOwnPropertyDescriptor(person,'name')
descriptor
//configurable: false
//enumerable: false
//value: "Alex"
//writable: false

3、变量类型检测

基本类型

  • undefined,表示未定义值,创建变量未初始化值
    undefined,不是关键字,建议使用void 0 代替。一般不会把变量值定义为undefined。

  • null,表示变量定义为空值
    null,关键字,直接赋值给变量

  • boolean,真假,true/false

  • string,字符串的UTF16编码,最长字符串编码长度为2^53-1
    一旦创建无法修改
    字符编码参考

  • number,浮点数
    共计2^64 - 2^53 + 3个值
    NaN、Infinity、-Infinity、+0、-0
    判断+0、-0的方法。1/x 是Infinity还是-Infinity
    浮点数不能用=====比较是否相等,而是使用比最小精度值小

 console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON)//true
  • symbol,一切非字符串的读写key的集合
    var mySymbol = Symbol("my symbol");

对象定义Symbol.iterator接口,可以使用for...of...循环遍历
具体参考ES6

  • object
    • Number
    • String
    • Boolean
    • Symbol
      Number、String、Boolean三个构造器,与new搭配产生对象,直接调用表示强制类型转换。
      Symbol不能使用new创建对象
      装箱操作
let symbolObj = (function(){return this}).call(Symbol('my symbol')
typeof symbolObj
//"object"

3.1 typeof

判断基本类型

typeof undefined; //'undefined'
typeof null; //"object"
typeof "string"; //'string'
typeof 1;  //"number"
typeof true; //"boolean"
typeof Symbol("my"); //"symbol"
typeof function() {}; //"function"
typeof []; //"object"
typeof {}; //"object"
typeof new String('string'); //'object'

无法区分null, {}, []等对象

3.2 instanceof

实例 instanceof 对象
判断变量是否是某个对象的实例,返回布尔值

var obj = {};
var foo = function() {};
obj instanceof Object; //true
foo instanceof Function; //true
Function instanceof Object; //true
Function instanceof Function; //true

let s = new String("string");
let str = "string";
s instanceof String; //true
str instanceof String; //false
[] instanceof Array; //true
[] instanceof Object; //true

3.3 constructor

根据实例的构造函数判断

[].constructor === Array //true
null.constructor === Object  //Cannot read property 'constructor' of null
undefined.constructor === undefined//Cannot read property 'constructor' of null

3.4 Object.prototype.toString.call();

Object.prototype.toString.call("");  //"[object String]"
Object.prototype.toString.call(new String(true));   //"[object String]"
Object.prototype.toString.call(1);  //"[object Number]"
Object.prototype.toString.call(new Number(1)); //"[object Number]"
Object.prototype.toString.call(true); //"[object Boolean]"
Object.prototype.toString.call(new Boolean(true)); ; //"[object Boolean]"

Object.prototype.toString.call([]);  //"[object Array]"
Object.prototype.toString.call({});  //"[object Object]"
Object.prototype.toString.call(function(){}); //"[object Function]"
Object.prototype.toString.call(new Date()); //"[object Date]"
//构造函数
Object.prototype.toString.call(Function); //"[object Function]"
Object.prototype.toString.call(Array); //"[object Function]"
Object.prototype.toString.call(Object); //"[object Function]"

class Person{}
class p extends Person{}
Object.prototype.toString.call(p); //"[object Function]"

3.5 Array.prototype.isPrototypeOf(obj)

用于判断对象的原型是否为数组

Array.prototype.isPrototypeOf([]); //true
Array.prototype.isPrototypeOf({}); //false
Object.prototype.isPrototypeOf({});//true
Object.prototype.isPrototypeOf([]);//true

3.6 Array.isArray()

精准的判断数组

Array.isArray([]) //true

3.7 综合函数判断函数

function type(o) {
    var t, c, n;  // type, class, name
    // 是null类型:
    if (o === null) return "null";
    // 是数值中的特殊类型: NaN :
    if (o !== o) return "NaN";
    // 使用 typeof 检测除去 "object"类型为的其他类型.   
    if ((t = typeof o) !== "object") return t;
    // typeof检测为"object"类型,则进一步检测
    // 可以检测出大部分内置类型
    if ((c = classof(o)) !== "Object") return c;
    // classof(o)返回为"Object"时,检测constructor
    if (o.constructor && typeof o.constructor === "function" &&
        (n = o.constructor.getName())) return n;
    // 无法识别的其他类型,默认为"Object"
    return "Object";
}
function classof(o) {
    return Object.prototype.toString.call(o).slice(8,-1);
};
    
// 返回function的名称,可能为""或者 null
Function.prototype.getName = function() {
    if ("name" in this) return this.name;
    return this.name = this.toString().match(/function\s*([^(]*)\(/)[1];
};

3.8 JQuery检测类型

jQuery.isArray(obj)测试对象是否为数组。
jQuery.isFunction(obj) 测试对象是否为函数。
jQuery.isEmptyObject(obj) jQuery 1.4 中,这个方法既检测对象本身的属性,也检测从原型继承的属性(因此没有使用hasOwnProperty)。
jQuery.isPlainObject(obj) 测试对象是否是纯粹的对象(通过 "{}" 或者 "new Object" 创建的)。
jQuery.isWindow(obj) 测试对象是否是窗口(有可能是Frame)。
jQuery.type(obj) 检测obj的数据类型。
jQuery.isNumeric(value) 确定它的参数是否是一个数字,包含16进制数

4、继承

父类

//定义一个类-动物
function Animal(name) {
  //属性
  this.name = name || "Animal";
  //实例方法
  this.sleep = function() {
    console.log(this.name + "正在睡觉!");
  };
}
//原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + "正在吃" + food);
};

4.1 原型的继承

父类的实例作为子类的原型

function Cat() {}
Cat.prototype = new Animal();
Cat.prototype.name = "cat";

var cat = new Cat();
cat.name; //'cat'
cat.eat('fish'); //cat正在吃fish
cat.sleep(); // cat正在睡觉!
cat instanceof Animal; //true
cat instanceof Cat;  //true

优点:

  • 实例是子类的实例也是父类的实例
  • 父类新增原型方法/原型属性,子类都能访问

缺点:

  • 必须在实例化后才能为原型新增属性和方法
  • 无法实现多继承
  • 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;
  • 创建子类实例时,无法向父类构造函数传参

4.2 构造函数的继承

使用父类的构造函数来增强子类实例。等于复制父类的实例属性给子类

function Cat(name) {
  Animal.call(this);
  this.name = name || "Tom";
}
var cat = new Cat();
cat.name; //Tom
cat.sleep() //Tom正在睡觉!
cat instanceof Animal; //false
cat instanceof Cat; //true

优点:

  • 子类实例共享父类的属性
  • 创建子类时可以向父类传参
  • 可以实现多继承(调用多个父类.call)
    缺点:
  • 实例只是子类的实例,不是父类的实例
  • 只能继承父类的实例属性和方法,不能继承原型的属性和方法
  • 无法实现函数复用。

4.3 实例继承

为父类实例添加新特性,作为子类实例返回

function Cat(name) {
  var instance = new Animal();
  instance.name = name || "Tom";
  return instance;
}

var cat = new Cat();
cat.name; //Tom
cat.sleep();//Tom正在睡觉!
cat instanceof Animal;  //true
cat instanceof Cat;  //false

优点:

  • 不限制调用方式,不论使用new或是直接函数调用都能返回实例
    缺点:
  • 实例时父类的实例,不是子类的实例
  • 不支持多继承

4.4 拷贝继承

将父类的属性循环拷贝到子类的原型上

function Cat(name) {
  var animal = new Animal();
  for (var p in animal) {
    Cat.prototype[p] = animal[p];
  }
}
Cat.prototype.name = name || "Tom";
var cat = new Cat();
cat.name; //Tom
cat.sleep();//Tom正在睡觉!
cat instanceof Animal;  //false
cat instanceof Cat;  //true

优点:

  • 支持多继承

缺点:

  • 效率低,内存占用高
  • 无法获取父类的不可枚举方法(for in)

4.5 组合继承

通过调用父类构造函数,继承父类构造函数的属性并保留传参优点,通过父类的实例作为子类的原型,实现函数复用

function Cat(name) {
  Animal.call(this);
  this.name = name || "Tom";
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat; //构造函数指向修复

var cat = new Cat();
cat.name; //Tom
cat.sleep();//Tom正在睡觉!
cat instanceof Animal;  //true
cat instanceof Cat;  //true
组合继承(不知道理解的对不对)

优点:

  • 既可以继承实例属性和方法,也可以继承原型属性和方法
  • 既是子类的实例也是父类的实例
  • 不存在引用属性共享问题
  • 可传参
  • 函数可以复用

缺点:

  • 调用了两次父类构造函数,生产了两份实例

4.6 寄生组合继承

通过寄生方式,砍掉父类的实例属性,调用两次父类构造函数的时候,不会初始化两次实例方法和属性,避免组合继承的缺点

function Cat(name) {
  Animal.call(this);
  this.name = name || "Tom";
}
(function() {
  //创建一个没有实例方法的类
  var Super = function() {};
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  Cat.prototype = new Super();
 Cat.prototype.constructor = Cat; //构造函数指向修复
})();

var cat = new Cat();
cat.name; //Tom
cat.sleep();//Tom正在睡觉!
cat instanceof Animal;  //true
cat instanceof Cat;  //true

没毛病,实现复杂

4.7 ES6继承

class Cat extends Animal{
    constructor(name = 'Tom'){
        super(name)
    }
}

var cat = new Cat();
cat.name; //'Tom'
cat.eat('fish'); //Tom正在吃fish
cat.sleep(); // Tom正在睡觉!
cat instanceof Animal; //true
cat instanceof Cat;  //true

5、更好的继承

封装一个方法,根据父类对象创建一个实例,该实例将会作为子类对象的原型

function  create(proto ,options){
    let  tmp={}
    tmp.__proto__=proto  //实例的原型指向父类的原型
    //传入的方法都挂载到新对象上,新对象作为子类的原型对象
    Object.defineProperties(tmp , options)
    return tmp
}

利用create实现对象的继承

Student.prototype=create(Person.prototype,{
     constructor:{
          value:Student
    }
    getGread:{
        value:function(){
                return this.grade
        }
    }
})

利用Object.create方法代替create

function Person(name , age){
    this.name=name;
    this.age=age
}
Person.prototype.getName=function(){
    return this.name
}
Person.prototype.getAge=function(){
    return this.age
}
function Student(name ,age ,grade){
    //构造函数的继承
    Person.call(this ,name ,age)
    this.grade=grade
}
// 原型继承
Student.prototype = Object.create(Person.prototype, {
    // 不要忘了重新指定构造函数
    constructor: {
        value: Student
    }
    getGrade: {
        value: function() {
            return this.grade
        }
    }
})

6、原型链

function add(){}
image.png
  1. Object构造函数,是函数,所以Object.__proto__指向Function.prototype
  2. 同理,构造函数Function也是函数Function.__proto__指向Function.prototype
  3. Function.prototype是原型对象,所以他的构造函数是Object,所以Function.prototype.__proto__指向Object.prototype

函数add是Function的实例对象,Function原型对象同时又是Object原型的实例。原型链和作用域链相同都是单向查找,因此add可以访问到Object的原型对象的toString方法
前端进阶系列,这一系列文章看了好几遍了,总算看懂了一点(2019-12-4)

你可能感兴趣的:(2018-10-22面向对象)