知识点梳理
变量类型
ECMAScript 中定义了 6 种原始类型:( ECMAScript 是一种规格, javascript 是 这种规格的实现 ) 6种: Boolean String Number Null Undefinded Symbol 注意:原始类型不包含 Object。 typeof typeof xxx得到的值有以下几种类型:undefined boolean number string object function、symbol
instanceof 用于实例和构造函数的对应。例如判断一个变量是否是数组,使用typeof无法判断, 但可以使用[1, 2] instanceof Array来判断。 因为,[1, 2]是数组,它的构造函数就是Array。
值类型 vs 引用类型
值类型变量包括 Boolean、String、Number、Undefined、Null, 引用类型包括了 Object 类的所有,如 Date、Array、Function 等
// 引用类型
var a = {x: 10, y: 20}
var b = a
b.x = 100
b.y = 200
console.log(a) // {x: 100, y: 200}
console.log(b) // {x: 100, y: 200}
提高题目 ①
function foo(a){
a = a * 10;
}
function bar(b){
b.value = 'new';
}
var a = 1;
var b = {value: 'old'};
foo(a);
bar(b);
console.log(a); // 1 这里是因为 Number 类型的 a 是按值传递, 而 Object 类型的b是按照共享传递是
console.log(b); // value: new
②
var obj = {
a: 1,
b: [1,2,3]
}
var a = obj.a
var b = obj.b
a = 2
b.push(4)
console.log(obj, a, b)
输出
{ a: 1, b: [ 1, 2, 3, 4 ] }
2
[ 1, 2, 3, 4 ]
虽然obj本身是个引用类型的变量(对象), 但是内部的a和b一个是值类型一个是引用类型, a的赋值不会改变obj.a,但是b的操作却会反映到obj对象上。
Symbol 类型
特点
- 唯一性: 通过 Symbol() 创建的符号都是唯一的
- 不可变性: 一旦创建就无法被更改
- 私有性: Symbol 可以作为 对象私有属性键, 这些属性不会被 for...in 或者 Object.keys() 等迭代方法访问 创建唯一的属性键
// 创建一个 Symbol
const myUniqueKey = Symbol('myUniqueKey');
// 使用 Symbol 作为对象的属性键
const myObject = {
[myUniqueKey]: 'This value is unique'
};
// 访问这个属性
console.log(myObject[myUniqueKey]); // 输出: This value is unique
// 尝试使用常规字符串作为键访问这个属性
console.log(myObject['myUniqueKey']); // 输出: undefined
避免属性名冲突 当你在不同的地方扩展同一个对象时,使用 Symbol 可以避免属性名冲突。
const mySymbol = Symbol('myProperty');
const obj1 = {
[mySymbol]: 'Value from obj1'
};
const obj2 = {
[mySymbol]: 'Value from obj2'
};
console.log(obj1[mySymbol]); // 输出: Value from obj1
console.log(obj2[mySymbol]); // 输出: Value from obj2
// 这两个属性是唯一的,即使它们在不同的上下文中使用了相同的 Symbol
私有属性 在JavaScript中,没有真正的私有属性,但 Symbol 可以用来模拟私有属性。
const privateKey = Symbol('privateKey');
class MyClass {
constructor() {
this[privateKey] = 'I am a private property';
}
getPrivateProperty() {
return this[privateKey];
}
}
const myInstance = new MyClass();
console.log(myInstance.getPrivateProperty()); // 输出: I am a private property
// 尝试直接访问这个属性会失败,因为它看起来像是一个普通属性
console.log(myInstance.privateKey); // 输出: undefined
使用 Symbol.for 和 Symbol.keyFor Symbol.for 允许你创建或访问一个全局的 Symbol,而 Symbol.keyFor 可以获取一个 Symbol 对应的字符串键。
// 创建一个全局 Symbol
const globalSymbol = Symbol.for('globalSymbol');
// 访问这个全局 Symbol
const retrievedGlobalSymbol = Symbol.for('globalSymbol');
console.log(globalSymbol === retrievedGlobalSymbol); // 输出: true
// 获取 Symbol 的字符串描述
console.log(Symbol.keyFor(globalSymbol)); // 输出: 'globalSymbol'
Symbol.for() 和 Symbol() ** 创建的有什么区别? 当你定义一个全局的 Symbol** 值时,通常是通过调用 Symbol.for(key) 方法, 其中 key 是一个字符串,用来描述这个 Symbol。 如果已经存在一个具有相同描述的 Symbol, 则 Symbol.for(key) 会返回那个已经存在的 Symbol, 否则会创建一个新的 Symbol 并将其注册到全局符号注册表中。 直接使用 Symbol() 创建的 Symbol 是一个全新的、唯一的 Symbol, 没有注册到全局符号注册表中, 它只存在于当前执行的代码环境中。
// ECMAScript 是一种规格 有六种类型
// 其中 Symbol 就是一种类型
// Symbol.for() 与 Symbol() 有什么区别
const Symbol1 = Symbol('xiaohei') // 注册到当前代码环镜
const Symbol2 = Symbol.for('xiaohei') // 创建到全局注册表
console.log(Symbol1, Symbol2)
console.log(Symbol.keyFor(Symbol1)) // unidentified
console.log(Symbol.keyFor(Symbol2)) // xiaohei
let obj = {
[Symbol1]: '我是obj'
}
console.log(obj[Symbol1])
console.log(obj[Symbol2])
// 通过 Symbol() 直接创建是唯一的
const symbol3 = Symbol('foo');
const symbol4 = Symbol('foo');
console.log(Symbol1 === Symbol2); // false
console.log(symbol3 === symbol4); // false
原型与原型链
所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(null除外) 所有的引用类型(数组、对象、函数),都有一个__proto__属性,属性值是一个普通的对象 所有的函数,都有一个prototype属性,属性值也是一个普通的对象 所有的引用类型(数组、对象、函数),__proto__属性值指向它的构造函数的prototype属性值
// 要点一:自由扩展属性
var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {}
fn.a = 100;
// 要点二:__proto__
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);
// 要点三:函数有 prototype
console.log(fn.prototype)
// 要点四:引用类型的 __proto__ 属性值指向它的构造函数的 prototype 属性值
console.log(obj.__proto__ === Object.prototype)
原型 所有 JS 对象 (包括函数) 都有一个内部属性 [[ Prototype ]], 这个属性指向一个对象或者null。 这个指向的**对象 ** , 我们称为对象的原型。
// 构造函数
function Foo(name, age) {
this.name = name
}
Foo.prototype.alertName = function () {
alert(this.name)
}
// 创建示例
var f = new Foo('zhangsan') // 通过 new 对象实例化 f 是 Foo 的一个实例, 也是一个对象
f.printName = function () {
console.log(this.name)
}
// 测试
f.printName()
f.alertName()
执行printName时很好理解,但是执行alertName时发生了什么? 这里再记住一个重点 当试图得到一个对象的某个属性时, 如果这个对象本身没有这个属性, 那么会去它的__proto__(即它的构造函数的prototype)中寻找, 因此f.alertName就会找到Foo.prototype.alertName。
接着上面的示例
f.printName()
f.alertName()
f.toString()
因为f本身没有toString(),并且 f.proto(即Foo.prototype)中也没有toString。 这个问题还是得拿出刚才那句话——当试图得到一个对象的某个属性时, 如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的 prototype)(在这里就是Car 对象 )中寻找。 小结
<script>
function Person() {
}
let p = new Person();
console.log(p);
if (p.__proto__ === Person.prototype) {
console.log(true);
}
if (Person.prototype.__proto__ === Object.prototype) {
console.log(true);
}
if (Object.prototype.__proto__ === null) {
console.log(true);
}
</script>
大家可以回头自行理解一下这份代码,理解什么是隐式原型,什么是显式原型,而原型链又是如何形成的。
图解
Person 是一个构造函数, 显示原型: 通过 prototype属性 可以得到 Person 的原型 隐式原型: 利用
__proto__属性 可以查找原型, 这个属性是对象类型数据的属性
原型链
在上面我们知道 __proto__ 属性是一个对象类型数据的属性,那么上面的 Person.prototype 是一个对象
那么该对象的 __proto__ 指向哪里?
我们需要找 __proto__ 指向哪里, 就需要找到构建该对象的构建函数,
就比如,我们要找 per1.__proto__ , 就需要找到 构建函数 Person , per1.__proto__ 指向的就是 Person.prototype ( 显示原型 )
而js中,对象的构造函数就是Object(),所以对象的原型对象,就是Object.prototype。
不过Object.prototype这个比较特殊,它没有上一层的原型对象,或者说是它的__proto__指向的是null。
函数也是一种对象
函数在js中,也算是一种特殊的对象,所以,可以想到的是,函数是不是也有一个__proto__属性? 答案是肯定的,既然如此,那就按上面的思路,先来找找函数对象的构造函数。 在js中,所有函数都可以看做是Function()的实例, 而Person()和Object()都是函数, 所以它们的构造函数就是Function()。 Function()本身也是函数,所以Function()也是自己的实例, 听起来既怪异又合理,但是就是这么回事。
console.log(Person.constructor === Function); // true
console.log(Object.constructor === Function); // true
console.log(Function.constructor === Function); // true
总的来说, 需要记住两点
- 所有对象的构建函数是 Object()
- 所有函数的构建函数是 Function()
- 构造函数是使用了new关键字的函数,用来创建对象,所有函数都是Function()的实例
- 原型对象是用来存放实例对象的公有属性和公有方法的一个公共对象,所有原型对象都是Object()的实例
code
// 函数类型数据
function foo() {
this.a = 1
console.log("foo");
}
// 添加公共方法
foo.prototype.sayHello = function (name) {
console.log("Hello world!", name);
};
const foo1 = new foo()
console.log("foo1隐式原型对象", foo1.__proto__);
console.log("foo函数显示原型", foo.prototype)
console.log("foo函数的隐式原型",foo.__proto__)
console.log("Function函数的原型", Function.prototype)
console.log(foo.__proto__ === Function.prototype
实现继承
// 定义一个构造函数 Dog,继承自 Animal
function Dog(name, breed) {
Animal.call(this, name); // 调用 Animal 的构造函数
this.breed = breed;
}
// 让 Dog 的原型指向新的 Animal 实例
// 非常关键
// 这行代码的作用是创建了一个新的空对象,其原型指向 Animal.prototype。
// 然后,这个新对象被赋值给 Dog.prototype。
// 这样,Dog 的实例就会继承 Animal.prototype 上定义的所有属性和方法
Dog.prototype = Object.create(Animal.prototype);
// 修正构造函数的引用
Dog.prototype.constructor = Dog;
// 在 Dog 的原型上添加一个方法
Dog.prototype.bark = function () {
console.log(`${this.name} barks.`);
};
// 创建 Animal 和 Dog 的实例
const animal = new Animal("Generic Animal");
const dog = new Dog("Rex", "German Shepherd");
// 演示原型链
animal.speak(); // Generic Animal makes a noise.
dog.speak(); // Rex makes a noise.
dog.bark(); // Rex barks.
// 验证一下
console.log(Dog.prototype === Animal.prototype) // false
console.log(Dog.prototype.__proto__ === Animal.prototype) // true
Dog.prototype = Object.create(Animal.prototype);的意思是
创建一个新的空对象, 其内部的[[Prototype]] 指向 Animal.prototype, 所以 console.log(Dog.prototype.proto === Animal.prototype) // true 所以 Dog.prototype 的原型是 Animal.prototype, 所以 Dog 的实例可以访问在 Animal.prototype 上定义的方法,例如 speak 方法。 注意后面需要重设 constructor 属性
Object.create() 我们传入一个对象{name: 'johan', age: 23}, 发现 obj 对象的隐式原型为 {name: 'johan', age: 23} 所以上面
let obj = Object.create({name: 'johan', age: 23})
console.log(obj)
console.log(obj.__proto__) // {name: 'johan', age: 23}
console.log(obj.name) // johan