语法糖
计算机语言中添加的某种语法,这种语法对语言的基本功能和执行结果没有影响,但能够使编程更加简洁和易于理解,从而提高程序的可读性和开发效率。
以下是一些常见的语法糖例子:
- 泛型:允许在定义类型时指定参数化类型,简化代码和提高代码的可读性。3
- 变长参数:允许函数定义时接收可变数量的参数,提高代码的灵活性。
- 条件编译:允许根据条件编译或排除某些代码段,优化代码性能。
- 自动拆装箱:自动将对象转换为基本类型,或将基本类型转换为对象,简化对象操作。
- 内部类:允许在类内部定义另一个类,提高代码的封装性和复用性。
使用class进行继承
每个类中包含了一个特殊的方法 constructor(),它是类的构造函数,这种方法用于创建和初始化一个由 class 创建的对象。
定义好类后,我们就可以使用 new 关键字来创建对象:
class Runoob
{
constructor(name, url)
{
this.name = name;
this.url = url;
}
}
let site = new Runoob("菜鸟教程", "https://www.runoob.com");
创建对象时会自动调用构造函数方法 constructor()。
我们还可以向类的方法发送参数:
class Runoob
{
constructor(name, year)
{
this.name = name;
this.year = year;
}
age(x)
{
return x - this.year;
}
}
let date = new Date();
let year = date.getFullYear();
let runoob = new Runoob("菜鸟教程", 2020);
document.getElementById("demo").innerHTML=
"菜鸟教程 " + runoob.age(year) + " 岁了。";
完整程序示例
class Person
{
constructor(name)
{
this.name = name;
}
drink()
{
console.log('喝水');
}
}
class Student extends Person
{
constructor(name, score)
{
// new Person();
super(name);
this.score = score;
}
introduce()
{
console.log(`我是${this.name}, 考了${this.score}分。`)
}
}
const student = new Student('张三', 99);
console.log('student', student);
student.introduce();
student.drink();
class Teacher extends Person
{
constructor(name, subject)
{
super(name);
this.subject = subject;
}
teach()
{
console.log(`我是${this.name}, 教${this.subject}。`)
}
}
const teacher = new Teacher('哈默', '前端开发');
console.log('teacher', teacher);
teacher.teach();
teacher.drink();
Object
JavaScript 中的 Object
是一种复杂数据类型,用于存储多个值作为属性。这些属性可以是原始值、函数或其他对象。JavaScript 对象提供了一种将相关数据组织在一起的方式,并且可以包括对这些数据进行操作的方法。
对象的基本结构
一个对象是由一系列键值对组成的,其中键是字符串(或符号),值可以是任何数据类型,包括原始值、函数或其他对象。
const person =
{
name: 'Alice',
age: 30,
greet: function()
{
console.log(`Hello, my name is ${this.name}.`);
}
};
在上面的例子中,person
是一个对象,它有三个属性:name
、age
和 greet
。name
和 age
是原始值属性,而 greet
是一个函数属性。
访问对象的属性
你可以使用点记法(.
)或方括号记法([]
)来访问对象的属性。
console.log(person.name); // 输出: Alice
console.log(person['age']); // 输出: 30
修改对象的属性
你可以直接给对象的属性赋值来修改它。
person.age = 31;
person['greet'](); // 输出: Hello, my name is Alice.
添加对象的属性
如果对象还没有某个属性,你可以通过赋值来添加它。
person.occupation = 'Engineer';
删除对象的属性
你可以使用 delete
操作符来删除对象的属性。
delete person.occupation;
对象的构造函数
在 JavaScript 中,你可以使用构造函数来创建具有相同属性和方法的多个对象。
function Person(name, age)
{
this.name = name;
this.age = age;
this.greet = function()
{
console.log(`Hello, my name is ${this.name}.`);
};
}
const alice = new Person('Alice', 30);
const bob = new Person('Bob', 25);
对象的原型链
JavaScript 中的对象通过原型链来继承属性和方法。每个对象都有一个内部链接指向它的原型对象,当试图访问一个对象的属性时,如果对象自身没有这个属性,那么 JavaScript 会在对象的原型上查找这个属性,以此类推,直到找到属性或到达原型链的末尾(通常是 null
)。
对象的遍历
你可以使用 for...in
循环来遍历对象的所有可枚举属性。
for (let key in person)
{
if (person.hasOwnProperty(key))
{ // 检查属性是否直接属于对象本身
console.log(key, person[key]);
}
}
Object
类型还提供了许多静态方法,用于操作对象,例如 Object.keys()
, Object.values()
, Object.entries()
, Object.assign()
, Object.create()
, Object.defineProperty()
, Object.getOwnPropertyDescriptor()
, 等等。这些方法可以在不创建对象实例的情况下直接通过 Object
调用。
总的来说,JavaScript 的 Object
类型非常灵活和强大,它允许你以多种方式组织和操作数据。
原型和原型链
①
②
1.原型:
在JavaScript中,每个函数都有一个特殊的属性叫做prototype
。这个属性指向一个对象(原型对象),这个对象作为原型,该函数所创建的所有对象都将继承这个原型对象的属性和方法。
function Person() {}
Person.prototype.name = 'John';
Person.prototype.age = 30;
Person.prototype.job = 'Developer';
var person1 = new Person();
console.log(person1.name); // 'John'
在这个例子中,Person
函数的prototype
属性指向一个包含name
,age
和job
属性的对象。当我们创建Person
的新实例person1
时,这个实例会从Person.prototype
中继承这些属性。
2.原型链:
JavaScript中的原型链是一个对象链,用于继承和链式查找属性和方法。当尝试访问对象的属性或方法时,如果这个对象本身没有这个属性或方法,那么JavaScript会在这个对象的原型(一个对象)上查找这个属性或方法。如果原型也不存在这个属性或方法,那么JavaScript会在原型的原型上查找,直到找到这个属性或方法,或者直到找到一个原型是null
的对象为止,这个过程就是原型链。
function Person() {}
Person.prototype.name = 'John';
Person.prototype.age = 30;
Person.prototype.job = 'Developer';
var person1 = new Person();
console.log(person1.name); // 'John'
// 修改原型对象
Person.prototype =
{
name: 'Alice',
age: 25,
job: 'Designer'
};
var person2 = new Person();
console.log(person2.name); // 'Alice'
console.log(person1.name); // 'John'
在这个例子中,person1
和person2
都是Person
的实例,但是它们的原型链指向的是Person.prototype
的不同实例。因此,person1
和person2
的name
属性是从不同的原型链中查找的,得到了不同的值。
③
- js分为函数对象和普通对象,每个对象都有__proto__属性,但是只有函数对象才有prototype属性
- Object、Function都是js内置的函数, 类似的还有我们常用到的Array、RegExp、Date、Boolean、Number、String
继承
原型链继承
function SuperType()
{
this.property = true;
}
//SuperType是一个构造函数,当用new关键字创建它的实例时,会为这个实例添加一个
property属性,并赋值为true`。
SuperType.prototype.getSuperValue = function()
{
return this.property;
}
//这里给 SuperType 的原型对象添加了一个方法 getSuperValue,
该方法返回实例的 property 属性的值。
function SubType()
{
this.subproperty = false;
}
//SubType是另一个构造函数,当创建它的实例时,会为这个实例添加一个subproperty属性,
并赋值为false`。
// 这里是关键,创建SuperType的实例,并将该实例赋值给SubType.prototype
SubType.prototype = new SuperType();
//这是继承的关键部分。这里,我们创建了一个 SuperType 的新实例,
//并将其赋值给SubType.prototype。这意味着当我们创建一个 SubType 的实例时,
//这个实例会继承 SuperType 实例的所有属性和方法。
//也就是说,SubType 的实例现在也有一个 property 属性和一个 getSuperValue 方法。
SubType.prototype.getSubValue = function()
{
return this.subproperty;
}
//这里给 SubType 的原型对象添加了一个方法 getSubValue,该方法返回实例的 subproperty 属性的值。
var instance = new SubType();
console.log(instance.getSuperValue()); // true
//首先,我们创建了一个 SubType 的新实例 instance。由于 SubType 的原型是
//SuperType 的一个实例,因此 instance 继承了 SuperType 的所有属性和方法。
//所以当我们调用 instance.getSuperValue() 时,它会返回 true,
//这是从 SuperType 继承的 property 属性的值。
原型链方案存在的缺点:多个实例对引用类型的操作会被篡改。
function SuperType()
{
this.colors = ["red", "blue", "green"];
}
function SubType(){}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
借用构造函数继承
使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型)
function SuperType()
{
this.color=["red","green","blue"];
}
function SubType()
{
//继承自SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,green,blue,black"
var instance2 = new SubType();
alert(instance2.color);//"red,green,blue"
核心代码是SuperType.call(this)
,创建子类实例时调用SuperType
构造函数,于是SubType
的每个实例都会将SuperType中的属性复制一份。
缺点:
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现复用,每个子类都有父类实例函数的副本,影响性能
组合继承
组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。
function SuperType(name)
{
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function()
{
alert(this.name);
};
function SubType(name, age)
{
// 继承属性
// 第二次调用SuperType()
SuperType.call(this, name);
this.age = age;
}
// 继承方法
// 构建原型链
// 第一次调用SuperType()
SubType.prototype = new SuperType();
// 重写SubType.prototype的constructor属性,指向自己的构造函数SubType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function()
{
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
缺点:
- 第一次调用
SuperType()
:给SubType.prototype
写入两个属性name,color。 - 第二次调用
SuperType()
:给instance1
写入两个属性name,color。
实例对象instance1
上的两个属性就屏蔽了其原型对象SubType.prototype的两个同名属性。所以,组合模式的缺点就是在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。
原型式继承
利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。
function object(obj)
{
function F(){}
F.prototype = obj;
//将 F 的原型对象设置为传入的 obj 对象。这意味着任何通过 F 创建的实例都将从 obj 对象
//继承其属性和方法。
return new F();
//这行代码使用 new 关键字调用 F 构造函数,从而创建一个新的对象。这个新对象的原型
//(__proto__)已经被设置为 obj,因此它会继承 obj 的所有属性和方法。
}
object()对传入其中的对象执行了一次浅复制
,将构造函数F的原型直接指向传入的对象。
var person =
{
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
//使用 object 函数创建了两个新对象:anotherPerson 和 yetAnotherPerson。
//这两个对象都继承自 person 对象,这意味着它们共享 person 对象的所有属性和方法。
//但是,这里的关键在于它们共享的是属性的引用,而不是属性的副本。
//当我们更改 anotherPerson.name 和 yetAnotherPerson.name 时,这并不影响 person.name,
//因为字符串在 JavaScript 中是不可变的,所以当我们更改这些属性时,实际上是创建了一个
//新的字符串值并赋值给了这些属性。
//然而,当我们向 anotherPerson.friends 和 yetAnotherPerson.friends 数组中添加元素时,
//我们实际上是在修改同一个数组。这是因为 friends 是一个数组,一个对象,而对象和数组在
//JavaScript 中是引用类型。所以 anotherPerson 和 yetAnotherPerson 继承的
//friends 属性实际上是对同一个数组的引用。
//因此,当你执行 anotherPerson.friends.push("Rob")
//和 yetAnotherPerson.friends.push("Barbie") 时,
//你实际上是在向 person.friends 数组添加元素,因为 anotherPerson.friends
//和 yetAnotherPerson.friends 都指向同一个数组。
//最后,当你弹出警告框显示 person.friends 时,你会看到数组包含了
//所有添加的元素:"Shelby,Court,Van,Rob,Barbie"。
//为了避免这种情况,你可以在 object 函数中创建一个新的数组,并将 obj.friends 的元素
//复制到新数组中,而不是直接设置 F.prototype = obj。这样,每个新创建的对象都会有一个
//独立的 friends 数组副本,而不是共享同一个数组。这可以通过遍历 obj.friends 并将
//每个元素推入新数组来实现。
缺点:
- 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
- 无法传递参数
另外,ES5中存在Object.create()
的方法,能够代替上面的object方法。
寄生式继承
核心:在原型式继承的基础上,增强对象,返回构造函数
function createAnother(original)
{
var clone = object(original); // 通过调用 object() 函数创建一个新对象
clone.sayHi = function(){ // 以某种方式来增强对象
alert("hi");
};
return clone; // 返回这个对象
}
函数的主要作用是为构造函数新增属性和方法,以增强函数
var person =
{
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
缺点(同原型式继承):
- 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
- 无法传递参数
寄生组合式继承
结合借用构造函数传递参数和寄生模式实现继承
function inheritPrototype(subType, superType)
{
var prototype = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本
prototype.constructor = subType; // 增强对象,弥补因重写原型而失去的默认的constructor 属性
subType.prototype = prototype; // 指定对象,将新创建的对象赋值给子类的原型
}
// 父类初始化实例属性和原型属性
function SuperType(name)
{
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function()
{
alert(this.name);
};
// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age)
{
SuperType.call(this, name);
this.age = age;
}
// 将父类原型指向子类
inheritPrototype(SubType, SuperType);
// 新增子类原型属性
SubType.prototype.sayAge = function()
{
alert(this.age);
}
var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);
instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]
这个例子的高效率体现在它只调用了一次SuperType
构造函数,并且因此避免了在SubType.prototype
上创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能够正常使用instanceof
和isPrototypeOf()
这是最成熟的方法,也是现在库实现的方法
混入方式继承多个对象
function MyClass()
{
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function()
{
// do something
};
Object.assign
会把 OtherSuperClass
原型上的函数拷贝到 MyClass
原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。
静态函数
JavaScript中的静态函数是定义在类上而不是类的实例上的函数。这意味着你不需要创建类的实例就可以调用这些函数。静态函数通常用于执行与类本身相关但不依赖于类实例的操作。
class MyClass
{
// 静态函数
static myStaticFunction()
{
return 'This is a static function!';
}
// 实例函数
myInstanceFunction()
{
return 'This is an instance function!';
}
}
// 调用静态函数
console.log(MyClass.myStaticFunction()); // 输出: 'This is a static function!'
// 创建类的实例
const instance = new MyClass();
// 调用实例函数
console.log(instance.myInstanceFunction()); // 输出: 'This is an instance function!'
// 注意:不能直接通过实例调用静态函数
// console.log(instance.myStaticFunction()); // TypeError: instance.myStaticFunction is not a function
静态函数通常用于执行与类本身相关的任务,例如工厂方法、配置类行为或执行一些不需要实例状态的操作。它们也可以用于组织工具函数或常量,这些函数或常量与类相关,但不需要访问或修改类的实例状态。
需要注意的是,静态函数不能访问或修改类的实例属性或方法,因为它们是绑定到类本身而不是类的实例的。同样,类的实例也不能直接调用静态函数。
常用静态函数
在JavaScript中,Object
本身并没有静态函数,但Object
类型上的确有一些常用的方法,例如Object.create()
, Object.defineProperty()
, Object.getOwnPropertyNames()
, 等等。这些方法用于操作对象,但它们不是通过对象实例调用的,而是直接通过Object
类型来调用,因此可以认为是“静态”的,尽管在JavaScript中并不使用“静态”这个词来描述它们。
然而,关于call
和apply
,这两个方法实际上是Function
对象的方法,而不是Object
的方法。它们用于调用一个函数,并允许你指定该函数运行时的this
值,以及传递给该函数的参数。这两个方法在函数式编程和回调函数中特别有用,因为它们允许你更灵活地控制函数的执行上下文。
Function.prototype.call()
call()
方法调用一个具有给定this
值的函数,以及作为一个数组(或类似数组对象)提供的参数。
语法:func.call(thisArg, arg1, arg2, ...)
thisArg
:在func
函数运行时使用的this
值。arg1, arg2, ...
:传递给func
函数的参数。
示例:
function greet(name, age) { | |
console.log(`Hello, my name is ${this.name} and I'm ${age} years old.`); | |
} | |
const person = { name: 'Alice' }; | |
greet.call(person, 'Bob', 30); // 输出: Hello, my name is Alice and I'm 30 years old. |
Function.prototype.apply()
apply()
方法调用一个具有给定this
值的函数,以及以一个数组(或类似数组对象)的形式提供的参数。
语法:func.apply(thisArg, [argsArray])
thisArg
:在func
函数运行时使用的this
值。argsArray
:一个数组或者类似数组的对象,其中的数组元素将作为单独的参数传给func
函数。
示例:
function sum(a, b, c) { | |
return a + b + c; | |
} | |
const numbers = [1, 2, 3]; | |
console.log(sum.apply(null, numbers)); // 输出: 6 |
在这两个例子中,call
和apply
都允许我们指定函数greet
和sum
执行时的this
值,并传递参数。它们的主要区别在于如何传递参数:call
接受一系列单独的参数,而apply
接受一个数组或类似数组的对象作为参数列表。
在你的任务中,如果要探讨Object
的一些常用方法以及call
和apply
的使用,你可以深入了解这些方法的用途和如何在实际编程中使用它们。同时,注意区分哪些方法是直接定义在Object
上的,哪些方法是定义在Function.prototype
上的。