一、new运算符特点
1.执行函数 new fn / new fn()
2.自动创建一个空对象 new Tab(); --->this
3.把创建的对象指向另外一个对象 let tab = new Tab(); 返回的是obj
4.把空对象和函数里的this衔接起来 this指向实例化对象
5.隐式返还this( this的创建与返回是隐性的 )
1)构造函数的作用?
1、使类的成员变量有合适的初值
2、构造函数的作用就是用来实例化的
3、名字和类名相同,没有返回值,不需要用户调用。
4、调用是在创建该类的对象的时候,由编译器自动调用
5、拷贝构造函数是指类的对象在创建的时候,能使用已有的对象初始化它。
或者在类的对象作为函数形参 / 函数返回值的时候,作为复制的机制需要自动拷贝类型值。
二、new简化工厂模式 = 构造函数
工厂模式: 没有constructor
function tab(){
let obj = {}; //new Tab() 时自动创建一个空对象,并且指向this,此条不需要
obj.hobby = function(){ } //new把obj指向this,可改为this.hobby
return obj; //隐式返还this,这条不需要了
}
let tab = new Tab(); //this指向obj即指向tab
构造函数:
function Tab(){
this.hobby = function(){ } //new把obj指向this,可改为this.hobby
}
let tab = new Tab();
tab.hobby();
构造函数更简洁
三、构造函数的原型(对象)
实例化对象由两部分构成: 构造函数、原型 共用一个this
function Tab(){
this.name = “张三”;
}
Tab.num = 10; //ES5的静态属性
Tab.prototype.pFor = function(){
console.log("pFor", this.name);
}
Tab.prototype.hobby = function(){
console.log("hobby", this.name);
}
let tab = new Tab();
console.log(tab.constructor === Tab); //true
console.log(tab instanceof Object); //true;
console.log(tab instanceof Tab); //true;instanceof专门比较一个对象是否为某个构造函数的实例
ES6写法:
不能这样写:
Tab.prototype = {
( constructor:tab,) //需要加上
pFor(){
console.log("pFor", this.name);
},
hobby(){
console.log("hobby");
}
}
constructor:
每个原型都有一个预定义属性 constructor , 指向构造函数
这样写会覆盖原本 constructor 属性,原型断了
可用于判断构造函数
let str = "abd"
if(str.constructor === String){ //String构造器
console.log("字符串")
}
回到原型
let tab1 = new Tab();
let tab2 = new Tab();
console.log(tab1.pFor === tab2.pFor) //true 这里的tab1、tab2相当于拿到指针
console.log(tab1.__proto__ === Tab.prototype); //true
1、约定首字母大写
2、原型是一个对象,属性在构造函数(this.name)上,方法在原型上
属性:是简单数据类型,不存在参数问题
原型本身是一个对象,对象有原型
1)没有原型可以是对象吗?
可以 Object.create(null) 没有构造函数和原型
Object.create(Object.prototype,{ }) 与 { } 相同
creat(null)的使用场景
1、
for..in
循环会遍历对象原型链上的属性,使用create(null)
就不必对属性进行检查了,当然,我们也可以直接使用Object.keys[]
2、
想节省hasOwnProperty
带来的一丢丢性能损失并且可以偷懒少些一点代码的时候3、自己定义
hasOwnProperty
、toString
方法,不必担心将原型链上的同名方法覆盖
详解Object.create(null)_嘿嘿-CSDN博客
2)为什么通用函数要放在原型?
1、方法放在构造函数,相当于工厂模式,每次调用都将在堆内存开辟新的空间
2、原型的作用解决了重复开辟新内存的冗余
四、原型链
- 原型本身是一个对象,也由本身和原型构成
- 先找本身的属性,如果没有就找原型链里的
arr.__proto__ == Array.prototype
true
arr.__proto__.__proto__ == Object.prototype
true
arr.__proto__.__proto__.__proto__ == null
true
function Drag(){
this.ele = "11";
}
Drag.prototype.ele = "22";
相当于:
let obj = new Object();
Object.prototype.ele = "33";
let drag = new Drag( );
console.log(drag.ele); //先返回本身的ele,没有的话再返回原型里的ele
console.log(Object.prototype.__proto__); //返回null,也是一个对象
// 当"22"也不存在时,返回“33”
同一个原型
面试题:instanceof的原理是什么?代码实现
- 专门比较一个对象是否为某个构造函数的实例,用它来检测对象的类型更可靠
- 不仅判断和构造函数是否有关,还判断是否和原型链有关
- 如果一个实例化对象A沿着原型链能找到B.prototype => A instanceof B为true
const instanceofA = (A, B) =>{
let p = A;
while(p){
if(p === B.prototype) return true;
p = p.__proto__;
}
return false;
}
instanceofA([],Array)
instanceofA([],Object)
instanceofA(1,Number)
五、仿写new运算符
function nyNew ( constructor, ...arg ){ //不定参argument,用rest参数传
let obj = { }; //1、创建一个新的对象
constructor = [].slice.call( obj,...arg); //2、将构造函数作用域赋给新的对象(即this指向新对象),arg传参
obj.__proto__ = constructor.prototype; //3、构造函数的原型指向对象的原型
return obj;
}
let tab = myNew(Tab);
console.log(tab.name,tab.pFor); //能拿到构造函数中的属性、方法
六、面向对象
继承
function Son(height){
Dad.call(this,height)
Dad.apply(this,[height])
Dad.bind(this)(height) //传参上面的区别
}
但是子拿不到父的原型(方法)
解决办法:
Son.prototype = Dad.prototype;
问题:若子类覆盖方法,父类也会被改变(浅拷贝(引用)对象是引用数据类型,这里只传地址)
解决办法——组合继承:用于重写子类方法,不影响父类
let Link = function(){ }; //新的构造函数
Link.prototype = Dad.prototype;
Son.prototype = new Link(); //问题:预定义属性constructor
解决办法: Son.prototype.constructor = Son; //因为constructor被覆盖了
七、深浅拷贝 / 传值和传址
引用/复杂数据类型 如何实现深拷贝(重新开辟地址传值)?
1、let obj2 = JSON.parse(JSON.stringify(obj)) //序列化开辟新地址
缺陷:丢失undefined和fn类型
2、function deepCopy(obj){
let newObj = Array.isArray(obj)? [ ] : { };
for(let i in obj){
if(obj.hasOwnProperty(i)){ //2、是否为自身属性(不是原型)返回true
if(typeof obj [ i ] === “object”){
if( obj [ i ] === null) newObj [ i ] = null; //1、解决缺陷问题
else newObj [ i ] = deepCopy(obj [ i ] );
}
else{
newObj [ i ] = obj [ i ] ;
}
}
}
return newObj;
}
缺陷1:null类型会被typeof当成对象
缺陷2:for in 不仅循环对象,还会把对象原型里的属性和方法循环
八、ES6类(实现层面上是方法,理解为类,并不是真正的类)
动态属性 constructor 和 动态方法hobby()只和实例化对象有关,和函数无关
静态属性 height 和 静态方法test()只和类有关 === 实例化对象drag没有height这个属性
继承也是同样的规则!!
class Drag{ //类名大写 //是一个function,不是object
static height = “178cm”; //静态属性 属于类的属性
static test(){ }; //调用Drag.test();
constructor(age){ //预定义属性
this.name = "张三";
this.age = 20;
}
hobby(){
......
}
或者将第二行换成 Drag.height = ”178cm“
let drag = new Drag(); //动态属性 用对象调用
console.log(Drag.height)
同时属于类和对象的:
Promise.resolve() // resolve属于类,不属于对象,无需实例化
继承
class LimitDrag extends Drag{ //ES6的继承没有es5传址的问题
constructor(age){
super(age); //一定要调用super
}
hobby(){ //重写父类hobby方法,会覆盖父方法
super.hobby(); //同时拿到父类子类的逻辑
console.log(”111“);
}
}
let drag = new LimitDrag(20) //先传入子类,通过super传入父类
LimitDrag.height; //可以获取
九、包装对象 / 判断类型
arr.push() arr 有原型,原型里有 push 方法
基本数据类型 string number boolean 没有原型
let str = new String(”a“) //生成实例化对象才有
let str = "a"
let arr = str.split(“ ”)
相当于
function mysplit(str,method,arg){
let temp = newString(str); // 自动生成临时对象
return temp[ method ] (arg); //对象原型里有方法,相当于temp调取split
// return 完会销毁temp包装对象
}
let arr = mysplit(str,“split”,“ ”)
判断类型常用方法
hasOwnProperty() 判断是否为对象自身属性
isPrototypeOf 判断原型链对象是否存在于指定对象实例中
constructor 判断实例化对象指向
function Person(){
this.name = "a";
} // Person.prototype.constructor 默认属性 所以写原型时不能覆盖
let a = new Person();
a.constructor === Person; //true
let arr = [ ];
arr.constructor === Array; //true
instanceof 不仅判断和构造函数是否有关,还判断是否和原型链有关
let arr = [ ], str = "xxx"
str instanceof String; //false
arr instanceof Array; //true
arr instanceof Object; //true
arr.constructor === Object; //false
typeof 也会从原型链上判断
typeof arr //返回object
typeof null //返回object
Object.prototype.toString.call() 精确判断数据类型
Object.prototype.toString.call(arr); //返回 [ object Array ]
Object.prototype.toString.call(obj); //返回 [ object Object ]
res === "[ object Array ]"