对象是 javascript 基本数据类型。对象是一种复合值: 它将很多值(原始值或者其它对象)聚合在一起,可通过名字访问这些值。
JavaScript 中,对象是拥有属性和方法的数据
JavaScript 对象是动态的可以新增属性和删除属性。
基本类型(原始类型):Undefined,Null,Boolean,Number,String
引用类型(对象类型):Object,Array,Date,RegExp,Function,特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)
// example1.js
var str = 'You can you up'
str.toLowwer()
console.log(str)
// example2.js
var str = 'You can you up'
str.name = 'LW'
str.toString = function(){}
console.log(str.name)
console.log(str.toString)
// example3.js
var person = {};
person.name = 'jeff';
person.age = 18;
person.sayName = function(){console.log(person.name);}
person.sayName();
delete person.name;
person.sayName();
var a = 1;
var b = true;
console.log(a == b)
var a = 'jozo';
var b = 'jozo';
console.log(a === b)
# example4.js
var person1 = {};
var person2 = {};
console.log(person1 == person2)
原始值:在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的 value 而已。
引用值:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量, 也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的改变都会反映在另一个身上。
# example5.js
var a = 1
var b = a
b=2
console.log(a)
var obj1 = {name:'jeff',age:18}
var obj2= obj1
obj2.age=20
console.log(`obj1=${obj1}`)
原始值:只是把变量里的值传递给参数,之后参数和这个变量互不影响。
引用值:对象变量它里面的值是这个对象在堆内存中的内存地址
# example6.js
var a=1
var b = {name:'jeff'}
function set(obj){
obj.age = 20
}
set(a)
set(b)
console.log(a)
console.log(b)
var a= `
产品:'这个需求什么时候做完?'
程序猿:'下班前做完'
第二天...
产品:'你昨天说下班需求做完,到现在你也没做完啊!',
程序猿:'我还没下班了。'
`
console.log(a.length) // 89
在这里 a 只是一个字符串,不应该存在属性和方法,但事实上他有自己的属性和方法,为什么?
其实在上面的例子中在读取字符串的时候会创建一个对象,但是这个对象只是临时的,所以我们称它为临时对象,学术名字叫包装对象。说它临时,是因为我们在读取它的属性的时候,js 会把这个 string 字符串通过 new String()方式创建一个字符串对象,有了对象自然就有了属性,但是这个对象只是临时的,一旦引用结束,这个对象就被销毁了。
var str = "You can you up"
console.log(str.length)
// 类似于下面的代码
console.log(new String(str).length)
同理数字、布尔值在读取属性的时候也可以通过自己的构造函数来创建自己的一个临时对象
思考
typeof null
typeof undefined
JavaScript 除 undefined 外 几乎所有事物都是对象或看成对象(number,string,boolean)
Javascript 中的函数即可以当普通函数调用,也可以是构造函数,普通函数通过 new 调用时, 它就是一个构造函数,即构造函数就是一个普通函数。
// example7.js
function Person(name, age) {
this.name = name
this.age = age
this.sayName = function(){
console.log(this.name,`..`)
}
}
let person1 = new Person('lw', 30)
person1.sayName()
// example8.js
function Person(name, age) {
this.name = name
this.age = age
this.sayName = function () {
console.log(this.name)
}
return 'hello'
return {
a: '1',
b: '2'
}
}
let person1 = new Person('lw', 30)
console.log(person1)
new 执行过程
let person = new Person('lw',30)
//伪代码
var obj = {}
obj.__proto__ = Person.prototype
Person.call(obj)
return obj
Javascript 内置构造函数有很多,主要是基本类型、引用类型(复合类型)
Object
var obj = new Object()
Function
var sum = Function("a","b","console.log(a+b)")
sum(1,2)
Array
var arr = new Array()
RegExp
var reg = new RegExp("\\d","g")
Number、 String、 Boolen
var num = new Number(20)
var str = new String("test")
var bool = new Boolean(false)
凡是通过 new Function()创建的对象都是函数对象,其它的都是普通对象。
// example9.js
var o1 = {};
var o2 = new Object();
var o3 = new f1();
function f1() {};
var f2 = function () {};
var f3 = new Function('str', 'console.log(str)');
console.log(typeof Object);
console.log(typeof Function);
console.log(typeof f1);
console.log(typeof f2);
console.log(typeof f3);
console.log(typeof o1);
console.log(typeof o2);
console.log(typeof o3);
上例中,f1,f2,f3 都是函数对象,o1,o2,03 都是普通对象, Object,Function 也是函数对象.
函数对象可以创建普通对象,普通对象无法创建函数对象。
function foo(){}
typeof foo
var f1 = new foo()
typeof f1
instanceof 运算符用来判断一个构造函数的 prototype 属性所指向的对象是否存在另外一个要检测对象的原型链上
var obj = {}
obj instanceof Object // 检测Object.prototype是否存在于参数obj的原型链上。
// example10.js
Function instanceof Object
Function instanceof Function
Object instanceof Function
Object instanceof Object
Function.prototype
Object.prototype
Object 继承自己, Function 继承自己,Object 和 Function 互相继承对方。
// example11.js
function Person() {}
var person1 = new Person()
console.log(person1 instanceof Person)
console.log(person1 instanceof Object)
console.log(person1.constructor == Person)
每个实例对象都有 constructor 属性,且 constructor 属性指向构造函数本身。
在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个 prototype 属性,这个属性是一个指针,他指向一个对象,这个对象就是原形对象。
每个函数对象都有 prototype 属性
原型对象,就是一个普通对象。原型对象就是 Person.prototype。
function Person() {
this.name = 'lw'
this.age = 30
this.sayName = function () {
console.log(this.name)
}
}
// 改写
function Person(){
}
Person.prototype.name = 'lw'
Person.prototype.age = 30
Person.prototype.sayName = function(){
console.log(this.name)
}
// 再次改写
Person.prototype = {
name: 'lw',
age: 30,
sayName: function () {
console.log(this.name)
}
}
在 默认情况下,所有的原型对象都会自动获得一个 constructorn 属性,这个属性是一个指针,指向 prototype 所在的构造函数
Person.prototype.constructor == Person
前面提到过
person1.constructor =Person
person1 有 constructor 属性,是因为 person1 是 Person 的实例。
那 Person.prototype 为什么有 constructor 属性?同理, Person.prototype (你把它想象成 A) 也是 Person 的实例。 也就是在 Person 创建的时候,创建了一个它的实例对象并赋值给它的 prototype,基本过程如下:
var A = new Person();
Person.prototype = A;
原型对象(Person.prototype)是构造函数(Person)的一个实例
但 Function.prototype 除外,它是函数对象,但它很特殊,他没有 prototype 属性(前面说道函数对象都有 prototype 属性)
function Person(){};
console.log(Person.prototype)
console.log(typeof Person.prototype)
console.log(typeof Function.prototype)
console.log(typeof Object.prototype)
console.log(typeof Function.prototype.prototype)
Function.prototype 为什么是函数对象呢?
var A = new Function ();
Function.prototype = A;
上文提到凡是通过 new Function( ) 产生的对象都是函数对象。因为 A 是函数对象,所以 Function.prototype 是函数对象。
主要用于继承
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
// example12.js
function Person() {}
Person.prototype.name = 'lw';
var person = new Person();
person.name = 'zk';
console.log(person.name)
delete person.name;
console.log(person.name)
console.log(person.toString())
console.log(Object.prototype)
可以让所有的实例对象共享它所包含的属性和方法,减少内存占用
// example13.js
function A() {
this.name = 'lw'
this.sayName = function () {
return this.name
}
}
function B() {
this.name = 'zk'
}
B.prototype.sayName = function () {
return this.name
}
var a1 = new A()
var a2 = new A()
var b2 = new B()
var b2 = new B()
console.log(a1)
console.log(a2)
console.log(b1)
console.log(b2)
我们通过使用构造函数 A 创建了两个对象,分别是 a1 , a2 ;通过构造函数 B 创建了两个对象 b1 , b2 ;我们可以发现 b1 , b2 这两个对象的那个 sayHello 方法都是指向了它们的构造函数的 prototype 属性的 sayHello 方法.而 a1 , a2 都是在自己内部定义了这个方法.
定义在构造函数内部的方法,会在它的每一个实例上都克隆这个方法;定义在构造函数的 prototype 属性上的方法会让它的所有示例都共享这个方法,但是不会在每个实例的内部重新定义这个方法 .如果我们的应用需要创建很多新的对象,并且这些对象还有许多的方法,为了节省内存,我们建议把这些方法都定义在构造函数的 prototype 属性上 当然,在某些情况下,我们需要将某些方法定义在构造函数中,这种情况一般是因为我们需要访问构造函数内部的私有变量
总结
每个构造函数都有一个原型对象,原型对象都包含一个指针,指向构造函数,而实例对象都包含一个指向原型对象的内部指针。
function Person(){}
Person.prototype.toString = function (){}
var person1 = new Person()
Person.prototype.constructor = Person
person1.constructor = Person
JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__的内置属性(隐式原形),它指向构造该对象的构造函数原型。
对象 person1 有一个 __proto__属性,创建它的构造函数是 Person,构造函数的原型对象是 Person.prototype ,所以: person1.__proto__ == Person.prototype
所有函数对象的 __proto__ 都指向 Function.prototype,它是一个空函数
// example14.js
function Person() {}
console.log(Person.__proto__ === Function.prototype) //true
console.log(Number.__proto__ === Function.prototype) // true
console.log(String.__proto__ === Function.prototype) // true
// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Object.__proto__ === Function.prototype // true
Object.constructor == Function // true
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true
所有的构造器都来自于 Function.prototype,甚至包括根构造器 Object 及 Function 自身。所有构造器都继承了·Function.prototype·的属性及方法。如 length、call、apply、bind
Function.prototype.__proto__ === Object.prototype
所有的构造器也都是一个普通 JS 对象,可以给构造器添加/删除属性等。同时它也继承了 Object.prototype 上的所有方法:toString、valueOf、hasOwnProperty 等
Object.prototype.__proto__ === null
var Person = new Object()
console.log(Person.prototype)
Person 是 Object 的实例,所以 Person 继承了 Object 的原型对象 Object.prototype 上所有的方法。
var arr = new Array()
console.log(Arr.prototype)
继承了 Array 的原型对象 Array.prototype 上所有的方法.
此处输出是空数组,可以使用 Object.getOwnPropertyNames 获取所有(包括不可枚举的属性)的属性名不包括 prototy 中的属性,返回一个数组:
var arrayAllKeys = Array.prototype; // [] 空数组
// 只得到 arrayAllKeys 这个对象里所有的属性名(不会去找 arrayAllKeys.prototype 中的属性)
console.log(Object.getOwnPropertyNames(arrayAllKeys));
/* 输出:
["length", "constructor", "toString", "toLocaleString", "join", "pop", "push",
"concat", "reverse", "shift", "unshift", "slice", "splice", "sort", "filter", "forEach",
"some", "every", "map", "indexOf", "lastIndexOf", "reduce", "reduceRight",
"entries", "keys", "copyWithin", "find", "findIndex", "fill"]
*/
var num = [1];
console.log(num.hasOwnPrototype())
因为 Array.prototype 虽然没这些方法,但是它有原型对象(__proto__)
Array.prototype.__proto__ == Object.prototype
function Person(){}
var person1 = new Person();
console.log(person1.**proto** === Person.prototype); // true
console.log(Person.prototype.**proto** === Object.prototype) //true
console.log(Object.prototype.**proto**) //null
Person.**proto** == Function.prototype; //true
console.log(Function.prototype)// function(){} (空函数)
var num = new Array()
console.log(num.**proto** == Array.prototype) // true
console.log( Array.prototype.**proto** == Object.prototype) // true
console.log(Array.prototype) // [](空数组)
console.log(Object.prototype.**proto**) //null
console.log(Array.**proto** == Function.prototype)// true
原型和原型链是 js 实现继承的一种模式
原型链的形成是靠__proto__,而非 prototype