目录
1.设置某一对象的某一原始属性:Object.definePropertype()
2.一次设置一个对象的多个原始属性 Object.definePropertys()
3.查看对象某个属性的原始属性 Object.getOwnPropertyDescriptor()
上篇回顾
一.js工作原理
1.设计初衷
js设计初衷为运行在浏览器端的脚本语言(主要就是操作dom)
2.单线程
(若为多线程可能会为多个线程同时操作一个dom,导致bug频出)
执行的代码线程只有一个,任务量大执行任务需要排队,如果某一个任务执行时间很长,那便会出现阻塞的问题,会导致用户体验差 ,为了解决阻塞问题,就出现了异步的方式。
3.异步
一般有两种情况使用异步:
setTimeOut(倒计时)某些功能需要等待多少时间后执行;
Ajax请求 通常也视为异步(请求数据时,数据返回时间不确定);
4.JS同步模式
5.异步模式
6.js垃圾回收机制
垃圾回收原因:
内存中存在一些不再需要的变量,这些 变量会产生垃圾,如果不处理便会一直积压,会影响到用户体验,也会造成内存泄露,所以为了解决内存泄露,js就有了垃圾回收机制
解决内存泄漏的方法:
间隔的不定期的寻找不再使用的变量并释放
回收方式:
标记清除(在变量生成时会打上标记,之后会观察变量是否需要,不需要时便会清除)
引用技术
变量被视为垃圾的情况:
没有被引用;多个对象相互引用形成闭环
变量不被视为垃圾的情况:
有具体的引用关系(闭包:函数内部含有函数,内部函数引用外部变量)
全局变量(随时会被使用,时刻待命)
function test(){
//在函数功能体中每一次执行完毕后,不被引用的变量会被销毁
var num = 0;
console.log(++num);
}
test();//1
test();//1
test();//1
二.对象
js中万物皆为对象
字符串,布尔,数值皆为对象,它们都可以调用对象,即调用对象原始方法toString(通过原型链进行调用),其实是在我们Object对象中,它们之所以可以调用对象,是因为它们三种类型也是通过对象方法创建的。null、undefined比较特殊,不能调用原始方法toString
var str = 'xioaming';
str.toStrong();//可以直接调用是因为在调用时js会自动进行装箱的操作,
//装箱:即自动使用包装器String()将str转换为对象,然后再使用tostring方法调用
//装箱后便会通过拆箱来返回字符串
var str1=new String('xioahong');//返回一个对象类型的字符串
console.log(str,str1);//xiaoming [String: 'xioahong']
1.如何创建对象
1.字面量方式
var stu = {//通过大括号来创建,括号内放置对象的描述(属性)和功能(方法)
name:'xiaoming',
height:180,
weight:60,
cook(){//方法,可以有多个
console.log(this.name+'会做饭');
//通过this指向本对象,加点属性名即可调用对象的某个属性
},
}
2.构造函数方式
使用Object或者使用自定义构造函数来初始化对象(例如Student)
var stu2 = new Object();
stu2.name="xiaohong";
stu2.height=160;
stu2.weight=50;
stu2.sayHellow=function(){
console.log(this.name+' say hello')
}
stu2.sayHellow();//xiaohong say hello
2.调用对象属性及方法
访问对象属性
点表示法,右侧必须是以属性名称命名的简单标识符
person.name
中括号表示法
中括号中必须是一个计算结果为字符串的表达式,可以通过变量访问属性,如果属性名中含语法错误的字符,或者属性名使用的是关键字或保留字,可以使用中括号
person["first name"]
//访问属性的方式
console.log(stu.name);
console.log(stu['name'])
//[]内必须为字符串,不加‘’时会被视为变量,然后全局查找这个变量
//方法调用
stu.cook();
3.如何删除、添加、修改对象属性
删除属性只能删除自定义属性,不能删除继承过来的属性
当添加对象中没有的属性时为新增属性,当对象有时,便为修改属性
//删除
delete stu.height;
//新增
stu.height='180';
//修改
stu.height='170';
4.如何遍历对象
/对象如何进行for in 遍历
//能被for in语句打印的属性称为可枚举属性(默认情况下自定义的属性都是可枚举的)
//不可枚举属性:js中基本包装类型的原型属性是不可枚举的,如Object, Array, Number等
// var num = new Number();
// for(var pro in num) {
// console.log("num." + pro + " = " + num[pro]);
// }
// 它的输出结果会是空。这是因为Number中内置的属性是不可枚举的,所以不能被for…in访问到。
for (k in stu){//in的左侧为属性的键,右边为需要遍历的对象,里面有多少属性就打印多少次
console.log(stu.k);//调用失败,不加括号被认为是变量
console.log(stu[k]);//调用成功
//代表对象属性的键,即name,weight,height等属性名的统称
//键的右侧即为值,所以name:‘xm’称为键值对
}
5.对象类型转换
1.转为布尔类型
// Object类型到Boolean类型
//空对象也可以直接转
var obj={};
console.log(Boolean(obj));//true
// 除了空引用(null)会转换为false,其他都被转换为true
var obj = {
name:"briup",
age:12
};
console.log(Boolean(obj));//true
2.转为字符串类型
// Object类型转换为String类型
console.log(obj.toString());//[object Object]
console.log(String(obj));//[object Object]
3.转为数值类型
// Object类型转换为Number类型
//如果没有重写valueOf,转数值类型会返回NaN,若重写则优先调用valueOf
var obj = {
name:"briup",
age:12,}
console.log(Number(obj));//NaN
4.重写toString方法与valueOf方法
//重写一个对象toString();
var obj = {
name:"briup",
age:12,
toString:function(){
return this.name+"--"+this.age;
}
};
console.log(obj.toString());//briup--12
console.log(String(obj));//briup--12
//重写toString方法后,包装器会自动调用toString,然后获取toString的值并返回
//所以在调用方法时要注意是否有重写toString方法
// valueOf() toString()
/*
1.两个方法都为对象的原始方法
2.valueOf为对象原始值,通常不会显示的调用,通常由js自动在后台调用
3.toString本身作用就是做字符串的转换,也会进行自动调用
4.如果只重写了valueOf()或者toString()方法,
无论是运算还是显示都调用该方法,并将返回值用Number()转换。
5.如果两个方法都重写了,运算时优先调用valueOf(),
并将返回值用Number()转换。进行显示时优先调用toString()
6.如果两个方法都没有重写,则返回NaN
*/
var obj = {
name:"briup",
age:12,
toString:function(){
return "100";
},
valueOf:function(){
return 10;
}
};
console.log(obj+2);//12
console.log(Number(obj));//10
var obj = {
name:"briup",
age:12,
toString:function(){
return "100";
},
}
console.log(obj+2);//1002 字符串类型的数值
console.log(Number(obj));//100
//可以通过valueOf实现累加,每次调用obj时,valueOf都会被调用
//因为num在valueOf中被引用,所以不会被垃圾回收机制回收
var obj = {
num:1,
valueOf:function(){
return this.num++
}
}
console.log(obj==1);//true
console.log(obj==2);//true
console.log(obj==3);//true
6.在对象中的检测属性和方法
1.检测属性方法
//检测属性方法
//in 关键词 检测某个属性是否是某个对象自有属性或者继承属性,返回布尔类型(true或false)
//hasOwnProperty(PropertyName) 检测某个属性是否是某个对象自有属性,继承的属性不行
//propertyIsEnumerable() 检测某个属性是否是某个对象自有属性,且可枚举
var obj = {
name:'xm'
}
console.log('name' in obj);//true
console.log(obj.hasOwnProperty('name'));//true
console.log(obj.propertyIsEnumerable('name'));//true
console.log(obj.hasOwnProperty('hhh'));//false 没有的就返回false
2.检测对象是否在同一个原型链中
原型链是一种关系,是实例对象和原型对象之间的关系,这种关系是通过原型(proto)来联系的。
//检测对象是否在同一个原型链中(同一个家族,有层次,逐级向下)
//isPrototypeOf(object) 检测一个对象是否存在与另一个对象的原型链上,
//在就返回true反之false(原型的指向) 检查传入的对象的原型
//instanceof 检测一个对象是否是某个构造函数的实例(new)
var str = new String('hh');
console.log(str instanceof String);//true
console.log(str instanceof Object);//true 沿着原型链一直向上找
function Animal(){}//自定义一个函数
var dog1 = new Animal();
console.log(dog1 instanceof Animal);//true
console.log(dog1 instanceof Object);//true 无论是什么对象它的顶层都是object
console.log(Animal.prototype.isPrototypeOf(dog1));//true
console.log(Object.prototype.isPrototypeOf(dog1));//true
function Dog(){}
var dog2 = new Dog();
console.log(Animal.prototype.isPrototypeOf(dog2));//false 不在一条原型链中
Dog.prototype = new Animal();//原型链继承,将子类构造函数的原型属性,指向父类的实例
//new animal相当于构造函数的父亲,而animal相当于他的家族,
//之所以指向他而不是指向animal,是因为生它的是他的父亲而不是家族
var dog2 = new Dog();
console.log(Animal.prototype.isPrototypeOf(dog2));//true
7.如何设置对象原始属性
1.设置某一对象的某一原始属性:Object.definePropertype()
//设置一个对象的原始属性
//Object.defineProperty 只能设置某一个对象中的某一个属性的原始属性
var obj = {
name:'xm',
age:12,
sex:'nan'
}
Object.defineProperty(obj,'name',{
configurable:false,//原始属性 默认可以被删除,false即不可被删除
enumerable:false,//原始属性 默认可以被枚举 false即不可以枚举
writable:false,//原始属性 默认可以被修改 false即不可以修改
value:'lisi'
})
console.log(obj);
console.log(obj.name);//可以调用,但因为不可枚举,所以在node中不展示
delete obj.name;//不可被删除
obj.name = 'xm';//不可以修改
console.log(obj.name);//{ age: 12, sex: 'nan' } lisi
for(var k in obj){
console.log(obj[k]);
}//不可枚举
2.一次设置一个对象的多个原始属性 Object.definePropertys()
//一次设置对象的多个原始属性
//Object.defineProperties
Object.defineProperties(obj,{
age:{
configurable:false
},
sex:{
configurable:false
}
})
3.查看对象某个属性的原始属性 Object.getOwnPropertyDescriptor()
//想知道某个属性的原始属性 Object.getOwnPropertyDescriptor()
var res = Object.getOwnPropertyDescriptor(obj,'name');
console.log(res);
// {value: 'lisi',writable: false,enumerable: false,configurable: false }
8.构造器属性
可以重写自定义属性set与get方法
在对象中,我们可能会设置一些比较奇怪的属性 _num、这种属性我们称为构造器函数,这种属性我们不希望直接通过外部访问 obj_num,我们希望自己去控制这个属性的访问逻辑 ,即obj_num可以访问到,并且会对它进行一些逻辑改变
var obj = {
_num:0
}
//obj_num可以访问到,且会返回数字:0
//构造器属性:可以重写自定义属性set与get方法
Object.defineProperty(obj,'num',{
//当我们没有设置这两个属性时。默认是隐式调用,如果设置了则会调用你设置的方法
set(num){
this._num = num
},//在num的值被设置时,调用
get(){
return '数字:'+this._num
}//在num的值被获取时,调用
})
console.log(obj.num);//数字:0
obj.num=5;
console.log(obj.num);//数字:5
9.值传递与引用传递(址传递)
基本数据类型的变量:
可以直接操作保存在变量中的实际的值
参数传递的时候传递的是实际值
引用数据类型的变量:
不能直接操作对象的内存空间,实际上是在操作对象的引用。可以为引用类型变量添加属性和方法,也可以改变和删除其属性和方法。
参数传递的时候传递的是引用地址。
10.对象序列化
对象序列化是指将对象的状态转换为字符串,也可以反序列化,将字符串还原为对象函数,RegExp,Error对象,undefined值不能序列化和反序列化。
JSON.stringify(obj) 将对象序列化为Json字符串,只能序列化对象可枚举的自有属性
JSON.parse(jsonStr) 反序列化
1. 常规转换
obj.toString()
2. 转换为json字符串
JSON.stringify(obj)
//用户输入信息
var user_info = {
username:'zs',
password:'123321',
age:'13'
}
//登录接口需要json格式的数据
//JSON.stringify(obj)
var j_str = JSON.stringify(user_info);//{"username":"zs","password":"123321","age":"13"}
console.log(j_str);//{"username":"zs","password":"123321","age":"13"}
// 返回json字符串,是一种有规则的字符串
//得到json字符串如何转回对象
var user_info2 = JSON.parse(j_str)//反序列化
console.log(user_info2);//{ username: 'zs', password: '123321', age: '13' }
console.log(user_info2.username);//zs
//自定义json字符串反序列化为对象
var str = '{ "username": "zs"," password": "123321", "age": 13 }';
var str_obj = JSON.parse(str);
console.log(str_obj.age);//zs 13
3. 转换为查询字符串
var qs = require('querystring')
qs.stringify(obj)
三.深入研究对象
1.JavaScript的装箱与拆箱
装箱:自动将基本数据类型转换为包装器类型;拆箱:自动将包装器类型转换为基本数据类型
//为什么一个数值类型可以调用对象方法?
//当我们的基本数据类型调用原型方法时,实际上js完成了装箱与拆箱的过程
var num1 = 123;
var num2 = Number(123);
var num3 = new Number(123);//三种创建数组的方式
console.log(num1);//123
console.log(num2);//123 数值类型
console.log(num3);//Number{123} 对象类型
//类型判断
console.log(typeof num2);//number
console.log(typeof num3);//object
console.log(num3.valueOf()==num2);//ture
console.log(num3.valueOf()===num2);//ture valueOf()对象原始值,可以重写
console.log(num3==num2);//true 在计算时会自动调用valueof,所以本行代码等价于上行
console.log(num3===num2);//flase
var str1 = String('xb');//调用一个函数
var str2 = new String('xh');//创建一个对象
console.log(str1); //xb
console.log(str2); //String{“xh”}
//举例
function Animal(name){//动物的构造函数
return name;
}
var dog1 = Animal('xiaohei');
var dog2 = new Animal('xiaobai');
console.log(dog1);//xaiohei 字符串
console.log(dog2);//Animal{...} 数值类型
使用包装器,就相当于调用包装器函数,使用new则相当于创建了一个对象
2.js创建可复用对象的方式
我们一般使用字面量的形式直接创建对象,但是这种创建方式对于创建大量相似对象的时候(比如一 群小狗各有各的名字颜色,但他们都是狗类),会产生大量的重复代码。但 js和一般的面向对象的语言不同,在 ES6 之前它没有类的概念。但是我们可以使用函数来进行模拟,从而产生出可复用的对象创建方式
工厂模式
工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。但是它有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码,而没有建立起对象和类型间的关系。
//工厂模式
function Dog(name,age){
var obj = new Object();
obj.name = name;
obj.age = age;
return obj;
}
var d1 = Dog('xb',2);
var d2 = Dog('xh',1);
console.log(d1);//{ name: 'xb', age: 2 }
console.log(d2.name);//xh
构造函数模式
js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么我们就可以把它称为构造函数。构造函数模式相对于工厂模式的优点是,所创建的对象和构造函数建立起了联系,因此我们可以通过原型来识别对象的类型。但是构造函数存在一个缺点就是,造成了不必要的函数对象的创建。
//构造函数方式创建对象
function Dog(name,age){
this.name=name;//接收传入的name属性
this.age=age;
// this.sayName=function(){
// console.log(this.name);
// } //每次创建对象时,都会去创建一个新的函数
this.sayName = this.sayName;
}
//我们可以把函数写在全局,可以避免重复创建函数的尴尬,但是这么使用封装较差
// function sayName(){
console.log(this.name)
}
var d1 = new Dog('xb',2);
var d2 = new Dog('xh',1);
console.log(d1);
console.log(d1._proto_.constructor);//返回一个对象的构造函数
console.log(d2.name);
d2.sayName();//调用方法
原型模式
因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。因此我们可以使用原型对象来添加公用属性和方法,从而实现代码的复用。这种方式相对于构造函数模式来说,解决了函数对象的复用问题。但是这种模式也存在一些问题,一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。
//原型模式
function Dog(){}
//在prototype中定义的属性和方法,所有实例对象都共享
Dog.prototype.name = 'xb';
Dog.prototype.age = 2;
Dog.prototype.text = {//在原型模式创建时,如果属性是引用数据类型,可能会有误
t:'is dog'
};
Dog.prototype.sayName = function(){
console.log(this.name);
}
var dog1 = new Dog();
var dog2 = new Dog();
dog1.text.t = "is white dog";
console.log(dog2.text.t);
dog1.sayName();
dog1.name='zhangshan';
console.log(dog1);
console.log(dog1.name);
组合使用构造模式+原型模式
创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存在一些问题,因此我们可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好。
//组合模式
//在ES5中比较完美的一种创建对象的方式
function Dog(name,age){
this.name = name;
this.age = age;
this.arr = [];//这里是重复创建的所以不会出现影响
}
Dog.prototype.sayName = function(){
console.log(this,name);
}
var d1 = new Dog('xb',2);
var d2 = new Dog('xh',1);