用构造函数创建对象
通过函数来创建对象: 因为函数内部的执行流程是固定的, 所以同一个函数生成的多个对象 一定是功能相同的
相当于流水线作业: 在保障质量的同时 保障合格率
new运算符: 一种辅助的手段
- 可以从代码的角度 简化构造函数的写法: 省略一些固定的代码
function 构造函数(参数) {
this.属性= 参数;
}
var 对象 = new 构造函数(参数);
function emp(name,age){
this.uname = name
this.uage = age
this.run = function(){
console.log('go')
}
}
var emp1 = new emp('taeyeon',18)
console.log(emp1)
对象创建的过程
通过构造函数创建对象时一共做了以下几件事:
1. 创建一个新的空对象
2. 继承构造函数的原型对象
2.1 用新对象调用构造函数
2.2 先将构造函数内所有this指向新对象
3. 给新对象添加新属性和新方法
4. 返回新对象的地址
function Student(name, age) {
this.sname = name;
this.sage = age;
this.intr = function () {
console.log(this.sname + this.sage);
}
}
// 1.创建一个新的空对象{}
// 2.新对象{}继承Student的原型对象Student.prototype,新对象{}.__proto__==Student.prototype
// 3.{}调用构造函数Student.prototype.constructor
// 3.1 构造函数被调用,创建Student函数作用域
// 3.2 Student函数作用域中的this指向创建的新对象{}
// 3.3 给this对象,也就是新对象{}添加属性(sname,sage,intr)
// 3.4 Student函数作用域中创建局部变量(name, age),接受参数('伍六七', 18)
// 3.5 Student函数作用域中的(name, age),赋值给this对象,也就是新对象{}的属性(sname,sage)
// 3.6 创建匿名函数对象function () { console.log(this.sname + this.sage); },赋值给this.intr
// 4. 返回新对象{}的地址值给aq
var aq = new Student('伍六七', 18);
console.log(aq);
原型对象和继承
原型对象:在函数创建时会同时创建一个对象,该对象是所有通过构造函数创建对象的父对象,包含了所有子对象共享的属性和方法
继承:子类具有父类的属性和方法,正常来讲类是抽象的,而对象是类的实例化,但是在js当中类也是一个对象
每个对象拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性
原型对象可以通过构造函数.prototype和通过构造函数创建对象的.__proto__访问
共有属性:保存在原型对象中,所有子对象共有的属性
自有属性:保存在子对象中,子对象自己所有的属性
// 构造函数
function Student(sname) {
this.sname = sname;
}
// 构造函数.prototype属性为原型对象
// 向Student的原型对象中添加共有方法intr()
Student.prototype.intr = function () {
console.log(`I'm ${this.sname}`)
}
// 向Student的原型对象中添加共有属性sage
Student.prototype.sage = 18;
var aq = new Student("伍六七");
var ss = new Student("梅十三");
console.log(aq);
console.log(ss);
// ap和ss的intr方法均为Student的原型对象中的intr
aq.intr();
ss.intr();
// aq和ss的父类是同一个对象
console.log(aq.__proto__ == ss.__proto__);//true
// aq的父类和Student的原型对象是一个
console.log(aq.__proto__ == Student.prototype)//true
原型链
原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推,这种关系常被称为原型链 (prototype chain)
每个实例对象(object)都有一个私有属性(称之为 proto)指向它的构造函数的原型对象(prototype),该原型对象也有一个自己的原型对象(proto) ,层层向上直到一个对象的原型对象为 null,根据定义,null 没有原型,并作为这个原型链中的最后一个环节
// Object是String对象和Number对象的原型对象
Object.prototype.intr = function(){
console.log('Object->intr');
}
var str = new String();
str.intr();
console.log(str);
// String构造函数的原型对象是String对象
console.log(str.__proto__);
// Object
console.log(str.__proto__.__proto__);
//null
console.log(str.__proto__.__proto__.__proto__);
var num = new Number();
num.intr();
console.log(num);
// Number构造函数的原型对象是Number对象
console.log(num.__proto__);
// Object
console.log(num.__proto__.__proto__);
// true
console.log(str.__proto__.__proto__ == num.__proto__.__proto__);
多态
js中的多态:一个属性或方法,不同的情况下表现出不同的状态
重写(override):当子类中的属性和方法与父类重名时,使用子对象自己的同名属性
多态是概念,重写是实现方法及效果
function Empolyee(ename, eage) {
this.ename = ename
this.eage = eage
}
Empolyee.prototype.role = '崔三娘'
var emp1 = new Empolyee('Taeyeon', 19);
var emp2 = new Empolyee('Yoona', 18)
console.log(emp1.role, emp2.role);
// 重写
emp2.role = '宁红夜'
console.log(emp1.role, emp2.role);
数据描述符
数据描述符是一个具有可写或不可写值的属性。
每个数据属性其实都是一个对象,每个属性对象中都包含一个value属性和三个权限属性。
- value 属性值
- writable:false, 控制是否可以修改
- enumerable:false, 控制是否可被for...in遍历
- configurable:false 控制是否能删除属性,是否可修改writable和enumerable属性
writable , enumerable , configurable 默认值均为true
enumerable设置为false后依然可以通过 对象.属性名 访问
configurable一旦设置为false则不可逆
//使用获取数据属性对象Object.getOwnPropertyDescriptor(对象名, "属性名")
var user = {
uid: 1001,
uname: '崔三娘'
}
for (var key in user) {
console.log(user[key]);
}
// 获取uid数据属性对象
var obj = Object.getOwnPropertyDescriptor(user, 'uid')
console.log(obj);
// 修改属性对象Object.defineProperty.(对象名, 属性名, {属性对象的属性})
// 设置user的uid属性只读
Object.defineProperty(user, 'uid', {
writable: false, // 控制是否可以修改
enumerable: false, // 控制是否可以被for...in遍历
configurable: false // 控制是否能删除属性,是否可修改前两个属性
})
user.uid = 1222
console.log(user); // uid未被修改
// 只能输出第一个属性
for (var key in user) {
console.log(user[key]);
}
// 删除uid属性
delete user.uid
console.log(user); // 删除失败
// configurable: false 控制writable和enumerable属性适合可以修改,一旦设置不可逆
Object.defineProperty(user, 'uid', {
writable: true, // 报错
enumerable: true // 报错
})
同时设置多个属性对象
// "use strict"
var user = {
uid: 1001,
uname: "崔三娘"
}
Object.defineProperties(user, {
uid: {
writable: false,
},
uname: {
writable: false,
},
// 语法糖: value: function(){}
intro: {
value() {
console.log(this.uid, this.uname)
},
},
});
user.intro();
user.uid = 1222; // 修改失败,在严格模式下报错
user.uname = '宁红夜'; // 修改失败,在严格模式下报错
console.log(user);
把设置放在对象的构造函数里,所有创建的对象都会被保护
function User(uid, uname) {
// this.uid = uid
Object.defineProperties(this, {
uid: {
value: uid,
writable: false,
enumerable: true,
configurable: false
},
// 如果对象中没有要设置的属性,会自动添加该属性,但是须要写完value+三个属性
// 通过defineProperty定义的新属性的writable和enumerable默认值是false
uname: {
value: uname,
configurable: false
}
})
}
var nhy = new User(1003, '宁红夜')
nhy.uid = 1 // 修改失败
console.log(nhy);
// 由于uname是通过defineProperty定义的新属性的,enumerable默认值是false,无法遍历
for (var key in nhy) {
console.log(nhy[key]);
}
访问器描述符
访问器描述符是由 getter/setter 函数对描述的属性。描述符只能是这两种类型之一,不能同时为两者。
访问器属性:每个访问器属性其实都是一个对象,每个属性对象中都包含getter和setter函数和二个权限属性
访问器属性不能直接定义,必须使用Object.defineProperty()
在访问属性值的会自动执行getter和setter函数,可以对属性值做一些中间处理
function User(uid, uname) {
this.uid = uid
this.uname = uname
}
var csn = new User(1002, '崔三娘')
Object.defineProperties(csn, {
// 定义一个新的访问器属性uage
uage: {
// 访问uage属性时会自动调用get方法
get: function () {
console.log('自动调用了uage的get()');
return this._uage
},
// 修改uage属性时会自动调用set方法
// set: function (value) {} 语法糖 -> set(value) {}
set(value) {
// value会自动接到要赋值的新值
console.log(`自动调用了uage的set(${value})`);
if (value >= 0 && value <= 3) {
this._uage = value
} else {
throw Error('年龄必须介于0~3之间')
}
},
enumerable: true,
configurable: false,
},
// 实际被访问的属性,当访问和修改uage时,实际是在对_uage.value操作
_uage: {
value: csn.uage,
writable: true,
enumerable: false,
configurable: false
}
})
// 访问uage属性值:都会自动调用get()方法
csn.uage
console.log(csn);
// 尝试修改uage的属性值为正确的值
csn.uage = 2
// _属性依旧可以通过 对象.属性名 来访问
console.log(csn._uage); //2
csn._uage = 20
console.log(csn); // 不报错
// 修改uage的属性值为错误的值
csn.uage = -2
console.log(csn); // 报错
防扩展
对象的extensible属性用于表示是否允许在对象中动态添加新的属性
Object.isExtensible() 方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)
Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性,不可扩展对象的原型是不可变的
// "use strict"
var user = {
uid: 1001,
uname: "胡桃"
}
// 新对象默认是可扩展的
let a = Object.isExtensible(user); // true
console.log(a);
// 变为不可扩展
Object.preventExtensions(user);
let b = Object.isExtensible(user); // false
console.log(b);
// 添加属性失败
user.uage = 18;
console.log(user);
// 依然可以删除属性,只是不能添加
delete user.uid;
console.log(user);
// 报错
// user.__proto__ = { obj: "obj" };
console.log(user);
密封
Object.seal()方法封闭一个对象,出了能修改现有属性的属性值,其他都不能修改。
Object.seal(obj),Object.seal()自动将对象的extensible:false ,configurable:false。
var obj = {
prop: function () { },
foo: 'bar'
}
// 添加新属性
// 可以更改或删除现有的属性
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop
console.log(obj);
var o = Object.seal(obj);
console.log(o === obj); //true
let a = Object.isSealed(obj)
console.log(a); // true
// 仍然可以修改密封对象的属性值
obj.foo = 'asd'
console.log(obj);
// 但是你不能将属性重新定义成为访问器属性
// Uncaught TypeError: Cannot redefine property: foo
Object.defineProperty(obj, 'foo', {
get() { return 'g' }
}) // throws a TypeError
// 除了属性值以外的任何变化,都会失败.
// 严格模式下会报错,非严格模式会静默失败
// 添加属性将会失败
obj.quaxxor = 'ttht'
// 严格模式下会报错,非严格模式会静默失败
// 删除属性将会失败
delete obj.foo
console.log(obj);
// 在严格模式下,这样的尝试将会抛出错误
function fail() {
'use strict';
delete obj.foo;
obj.sparky = 'arf'
}
fail()
// 通过Object.defineProperty添加属性将会报错
Object.defineProperty(obj, 'ohai', {
value: 17
}); // throws a TypeError
Object.defineProperty(obj, 'foo', {
value: 'eit'
}); // 通过Object.defineProperty修改属性值
冻结
Object.freeze() 方法可以冻结一个对象,一个被冻结的对象再也不能被修改。
自动将对象的extensible : false,configurable : false,writable : false。
冻结对象
var obj = {
prop: function() {},
foo: 'bar'
};
// 新的属性会被添加, 已存在的属性可能
// 会被修改或移除
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;
// 作为参数传递的对象与返回的对象都被冻结
// 所以不必保存返回的对象(因为两个对象全等)
var o = Object.freeze(obj);
o === obj; // true
Object.isFrozen(obj); // === true
// 现在任何改变都会失效
obj.foo = 'quux'; // 静默地不做任何事
// 静默地不添加此属性
obj.quaxxor = 'the friendly duck';
// 在严格模式,如此行为将抛出 TypeErrors
function fail(){
'use strict';
obj.foo = 'sparky'; // throws a TypeError
delete obj.quaxxor; // 返回true,因为quaxxor属性从来未被添加
obj.sparky = 'arf'; // throws a TypeError
}
fail();
// 试图通过 Object.defineProperty 更改属性
// 下面两个语句都会抛出 TypeError.
Object.defineProperty(obj, 'ohai', { value: 17 });
Object.defineProperty(obj, 'foo', { value: 'eit' });
// 也不能更改原型
// 下面两个语句都会抛出 TypeError.
Object.setPrototypeOf(obj, { x: 20 })
obj.__proto__ = { x: 20 }
冻结数组
let a = [0];
Object.freeze(a); // 现在数组不能被修改了.
a[0]=1; // fails silently
a.push(2); // fails silently
// In strict mode such attempts will throw TypeErrors
function fail() {
"use strict"
a[0] = 1;
a.push(2);
}
fail();
被冻结的对象是不可变的。但也不总是这样。下例展示了冻结对象不是常量对象(浅冻结)。
obj1 = {
internal: {}
};
Object.freeze(obj1);
obj1.internal.a = 'aValue';
obj1.internal.a; // 'aValue'
对于一个常量对象,整个引用图(直接和间接引用其他对象)只能引用不可变的冻结对象。冻结的对象被认为是不可变的,因为整个对象中的整个对象状态(对其他对象的值和引用)是固定的。注意,字符串,数字和布尔总是不可变的,而函数和数组是对象。
要使对象不可变,需要递归冻结每个类型为对象的属性(深冻结)。当你知道对象在引用图中不包含任何 '环' (循环引用)时,将根据你的设计逐个使用该模式,否则将触发无限循环。对 deepFreeze() 的增强将是具有接收路径(例如Array)参数的内部函数,以便当对象进入不变时,可以递归地调用 deepFreeze() 。你仍然有冻结不应冻结的对象的风险,例如[window]
// 深冻结函数.
function deepFreeze(obj) {
// 取回定义在obj上的属性名
var propNames = Object.getOwnPropertyNames(obj);
// 在冻结自身之前冻结属性
propNames.forEach(function(name) {
var prop = obj[name];
// 如果prop是个对象,冻结它
if (typeof prop == 'object' && prop !== null)
deepFreeze(prop);
});
// 冻结自身(no-op if already frozen)
return Object.freeze(obj);
}
obj2 = {
internal: {}
};
deepFreeze(obj2);
obj2.internal.a = 'anotherValue';
obj2.internal.a; // undefined
对比Object.seal()
用Object.seal()密封的对象可以改变它们现有的属性。使用Object.freeze() 冻结的对象中现有属性是不可变的。
创建新对象
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto
Object.create(proto,[propertiesObject])
var A = {
x : 0,
y : 0
}
var a = Object.create(A);
console.log(a); // {}
console.log(a.__proto__); // {x:0,y:0}
使用 Object.create 的 propertyObject参数
var o;
// 创建一个原型为null的空对象
o = Object.create(null)
o = {}
// 以字面量方式创建的空对象就相当于:
o = Object.create(Object.prototype)
console.log(o);
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);
}
}
});
console.log(o);
function Constructor() { }
o = new Constructor();
// 上面的一句就相当于:
o = Object.create(Constructor.prototype);
// 当然,如果在Constructor函数中有一些初始化代码,Object.create不能执行那些代码
// 创建一个以另一个空对象为原型,且拥有一个属性p的对象
o = Object.create({}, { p: { value: 42 } })
console.log(o);
// 省略了的属性特性默认为false,所以属性p是不可写,不可枚举,不可配置的:
o.p = 24
console.log(o.p);// 42
o.q = 12
console.log(o); //{q: 12, p: 42}
for (var prop in o) {
console.log(prop);
}
//'q'
let a1 = delete o.p
console.log(a1); // false
//创建一个可写的,可枚举的,可配置的属性p
o2 = Object.create({}, {
p: {
value: 42,
writable: true,
enumerable: true,
configurable: true
}
});
this的对象指向
当前调用函数时的对象
全局中的this
// 在浏览器中全局中的this和全局函数中的this全都是指向Window
console.log(this);
function fun() {
// 在浏览器中this和全局函数中的this全都是指向Window
console.log(this)
}
fun();
对象内
对象内this指向掌握一个原则,谁调用就指向谁
// obj.fun() this->obj
var obj = {
name: 'zs',
fun: function () {
console.log(this);
}
};
obj.fun();
// new Fun() this->new新对象
function Student(sname,sage){
this.name = 'zs',
this.fun = function(){
console.log(this);
}
}
new Student().fun();
// 构造函数.prototype.fun=function(){this->?} this->子对象.fun()中的子对象
String.prototype.intr = function(){
console.log(this);
}
var str = new String('str');
str.intr();