记录一些前端知识。
对象的定义
无序属性的集合,其属性可以包含基本值、对象或者函数,。对象的每个属性或方 法都有一个名字,而每个名字都映射到一个值,每个对象都是基于一个引用类型创建的。
数据类型
基本类型值:Undefined、Null、Boolean、Number、String。
引用类型值:Obejct
理解对象
创建自定义对象的一个最简单的方式就是创建一个Object的实例,然后给它添加属性和方法。
//方式(1)
let a = new Object();
//属性
a.name = "ssssslf";
a.age = 24;
//方法
a.getName = function(){
console.log(this.name)
}
//方式(2)
let a = {
name:"ssssslf",
age:24,
getName:function(){
console.log(this.name)
}
}
复制代码
属性
数据属性和访问器属性
数据属性:数据属性包含一个数据值的位置。在这个位置可以读取和写入值
- Configurable:默认为true,表示是否可以删除属性修改属性。
- Enumerable:默认为true,表示是否可以通过 for-in 循环返回属性。
- Writable:默认为true,表示能否修改属性的值。
- Value:默认为undefined,包含中国属性的值。
要修改属性的默认特性,必须使用Object.defineProperty()方法
例子:
//参数:属性所在对象,属性名称,描述符对象
Object.defineProperty(a,"name",{
writable:false,//不能修改属性值,只读
value:"slf",
})
console.log(a.name);//slf
a.name = "sslf";
console.log(a.name);//slf
Object.defineProperty(a,"name",{
writable:true,//能修改属性值
value:"slf",
})
console.log(a.name);//slf
a.name = "sslf";
console.log(a.name);//sslf
复制代码
其余方式类似。
注意:如果修改Configurable,讲其改为false,再将其改为true,会报错。
访问器属性:没有数据值,但是有getter,setter函数
- Configurable:同上
- Enumerable:同上
- get:默认值undefined,读取属性时调用的函数
- set:默认值undefined,写入属性时调用的函数
例子:
let a = {
name:"ssssslf",
age:24,
getName:function(){
console.log(this.name)
}
}
Object.defineProperty(a,"_name",{
get:function(){
return this.name;
},
set:function(newValue){
if(newValue === "ssslf"){
this.name = newValue;
}
}
})
a._name = "sslf";
console.log(a);//{name: "ssssslf", age: 24, getName: ƒ}
a._name = "ssslf";
console.log(a);//{name: "ssslf", age: 24, getName: ƒ}
复制代码
以上例子是ECMScript5,旧有方法用: defineGetter , defineSetter.
a.__defineGetter__("_name",function(){
return this.name
})
a.__defineSetter__("_name",function(){
if(newValue === "ssslf"){
this.name = newValue;
}
})
复制代码
Object.defineProperty 设置单个属性,Object.defineProperties设置多个属性
let a = {};
Object.defineProperties(a,{
//数据属性
name:{
value:"slf"
},
age:{
value:"24",
},
//访问器属性
_name:{
get:function(){
return this.name
},
set:function(newValue){
if(newValue === "ssslf"){
this.name = newValue;
}
}
}
})
复制代码
读取属性:Object.getOwnPropertyDescriptor
var descriptor = Object.getOwnPropertyDescriptor(a,"name")
console.log(descriptor.value)//slf
console.log(descriptor.configurable)//false
复制代码
创建对象
工厂模式
function createPerson(name,age){
//显示创建对象
var o = new Object();
o.name = name;
o.age = age;
o.getName = function(){
return this.name
}
return o;
}
var slf = createPerson("slf",24);
复制代码
- 优点:解决创建多个相似对象的问题
- 缺点:没有解决对象识别问题,不知道对象类型
构造函数模式
//没有显示创建对象,直接将属性方法赋值给this,没有return语句
//构造函数,函数名称大写
function Person(name,age){
//属性
this.name = name;
this.age = age;
//方法
this.getName = function(){
return this.name;
}
}
//创建Person实例,必须使用new操作符
var slf = new Person("slf",24);
复制代码
打印slf
slf是Person的一个实例,slf有一个constructor(构造函数)属性,该属性指向Person 即console.log(slf.constructor == Person) //true
在工厂模式中是无法知道对象类型的,但在这里是可以知道对象类型的。判断对象类型的方式可以用instanceof
console.log(slf instanceof Object) //true 所有的对象都继承Object
console.log(slf instanceof Person)//true
复制代码
构造函数的调用方式
(1) 当做构造函数使用 (2) 当做普通函数调用 (3) 在另一个对象的作用域中调用
//(1)
var person = new Person("slf",24);
person.getName();
//(2)
Person("slf1",25);
window.geName();
//(3)
var other = new Object();
Person.call(other,"slf2",26);
other.getName();
复制代码
- 缺点:每一个方法都要在每一个实力上创新创建一遍
var slf1 = new Person("slf1",24);
var slf2 = new Person("slf2",24);
复制代码
上诉代码 slf1,slf2都有getName()方法,但是两个对象的getName()并不是同一个Function对象。可以将getName()提取出来到构造函数外部。
function Person(name,age){
this.name = name;
this.age = age;
this.getName = getName;
}
function getName(){
console.log(this.name)
}
//问题:作为全局作用域的函数却只有只被某个对象调用
复制代码
原型模式
每创建的一个函数都有一个prototype属性,该属性为指针,指向一个对象,该对象有一个constructor属性,即
console.log(Person.prototype.constructor==Person)//true
复制代码
而创建的函数又是Function的实例,也就是
console.log(Person._proto_==Function.prototype)//true
复制代码
_proto_我的理解就是相当于找他们的爸爸
普通类型有_proto_属性,引用类型有_proto_和prototype属性,即
var abc = new Person("slf",24)
console.log(abc.__proto__ == Person.prototype)//true
console.log(abc.prototype) //undefined
复制代码
原型模式和构造函数模式的不同在于不用在构造函数中定义对象实例的信息,可以直接将这些信息添加到原型对象当中,即prototype中
function Person(){}
//这些属性和方法被所有的实例共享
Person.prototype.name = "slf";
Person.prototype.age = 24;
Person.prototype.getName = function(){
console.log(this.name);
}
var p1 = new Person();
p1.getName();//slf
var p2 = new Person();
console.log(p1.getName == p2.getName)//true
复制代码
查找p1.getName()时候,先查找P1中是否有getName()方法,没有,然后找p1.proto.getName
实例可以访问原型中的值,却无法改变原型当中的值,只能改写原型当中的值,即
function Person(){}
//这些属性和方法被所有的实例共享
Person.prototype.name = "slf";
Person.prototype.age = 24;
Person.prototype.getName = function(){
console.log(this.name);
}
var p1 = new Person();
p1.getName();//slf
var p2 = new Person();
p2.name = "slf1"
p2.getName();//slf1
复制代码
hasOwnProperty() 可以检验一个属性是存在原型中还是实例中
p1.hasOwnProperty("name") //false
p2.hasOwnProperty("name") //true
复制代码
in 不可以检验一个属性是存在原型中还是实例中
("name" in p1) //true
("name" in p2) //true
复制代码
判断一个属性存在对象中还是原型中
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name)&&(name in object)
}
hasPrototypeProperty(p1,"name")//true
hasPrototypeProperty(p2,"name")//false //存在实例中
复制代码
- 另外一种原型语法
相当于重写整个原型对象,这个时候constructor不再指向Person,instanceof 无法确定对象类型
function Person(){}
Person.prototype={
name:"slf",
age:24,
getName:function(){
console.log(this.name)
}
}
复制代码
如果要使用constructor
function Person(){}
Person.prototype={
constructor:Person,
name:"slf",
age:24,
getName:function(){
console.log(this.name)
}
}
复制代码
这种方式会造成constructor可以枚举,enumerable=true
var p3 = new Person();
for(let i in p3){console.log(i)}
//constructor name age getName
复制代码
但是原生的constructor不可枚举
Object.defineProperty(Person.prototype,"constructor",{
enumerable:false,
value:Person
})
复制代码
原型的动态
我们对原型对象所做的任何修改都能够立即在实例中体现,即使先创建实例再修改原型。 但是如果重写Person.prototype就不会了
- 缺点:对于引用类型,因为属性值共享,当某个实例修改属性,所有实例的该属性值都会被改变。
function Person(){}
Person.prototype={
constructor:Person,
name:"slf",
friends:["s","l"],
age:24,
getName:function(){
console.log(this.name)
}
}
var p1 = new Person();
var p2 = new Person();
p1.friends.push("f");
p1.friends//["s","l","f"]
p2.friends//["s","l","f"]
复制代码
组合使用构造函数模式和原型模式
构造模式用于定义属性,原型模式用于定义方法和共享属性,这样每个实例属性都有自己的一份实例属性副本,但又可以共享方法的引用,节约内存
复制代码
function Person(name,age){
//属性
this.name = name;
this.age = age;
this.friends = ["s","l"],
}
Person.prototype={
constructor:Person,
getName : function(){
return this.name;
}
}
var p1 = new Person("slf1",22);
var p2 = new Person("slf2",23);
p1.friends.push("f");
p1.friends//["s","l","f"]
p2.friends//["s","l"]
p1.getName == p2.getName //true
复制代码
动态原型模式
通过判断某个方法是否有效,决定是否要初始化原型
复制代码
function Person(name,age){
//属性
this.name = name;
this.age = age;
this.friends = ["s","l"],
//只有初次调用构造函数才会执行
if(typeof this.getName !='function'){
Person.prototype.getName= function(){
return this.name;
}
}
}
复制代码
寄生构造函数模式
创建一个封装创建对象,返回新创建对象的函数
复制代码
function Person(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.getName = function(){
return this.name;
}
return o;
}
var p1 = new Person("slf",24);
复制代码
这个模式的用法,创建一个具有额外方法的特殊数组,但不直接修改Array函数
function SpecialArray(){
var values = new Array();
//添加值
values.push.apply(values,arguments);
//添加方法
values.toPipedString = function(){
return this.join("|");
};
return values
}
var names = new SpecialArray("s","l","f");
names.toPipedString();//s|l|f
复制代码
构造函数返回的对象与构造函数外部创建的对象没有什么不同,不能用instanceof确定对象类型
稳妥构造函数模式
1.创建对象实例不用this.
2.不能使用new操作符调用构造函数
复制代码
function Person(name,age){
var o = new Object();
o.getName = function(){
return name;
}
return o;
}
var p1 = Person("slf",24);
复制代码