[参考文章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()
- ES5中定义的方法
Object.create(proto[,propertiesObject])
- 第一个参数是新创建对象的原型,任意的对象(包括原型对象)
- 第二个参数是定义对象的属性,可选
var o =Object.create({name:'x',age:11})
//o继承了原型的属性name和age
o.__proto__ //{name:'x',age:11}
- null为原型
var n = Object.create(null);
//n不继承任何属性或方法,包括toString()方法
- 创建空对象,同
{}
、new Object()
,继承Object的原型即可
var o =Object.create(Object.prototype)
同理Date、Array等对象可以通过其原型创建新对象
-
定义属性
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.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/falsestring
,字符串的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(){}
- Object构造函数,是函数,所以
Object.__proto__
指向Function.prototype
; - 同理,构造函数Function也是函数
Function.__proto__
指向Function.prototype
; -
Function.prototype
是原型对象,所以他的构造函数是Object,所以Function.prototype.__proto__
指向Object.prototype
函数add是Function的实例对象,Function原型对象同时又是Object原型的实例。原型链和作用域链相同都是单向查找,因此add可以访问到Object的原型对象的toString方法
前端进阶系列,这一系列文章看了好几遍了,总算看懂了一点(2019-12-4)