Javascript笔记

本文围绕JavaScript展开,介绍了语法糖,如泛型、变长参数等。详细阐述了Object类型,包括其结构、属性操作、构造函数、原型链及遍历。还介绍了多种继承方式,如原型链继承、组合继承等,以及静态函数和常用静态方法,如call和apply的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

语法糖

计算机语言中添加的某种语法,这种语法对语言的基本功能和执行结果没有影响,但能够使编程更加简洁和易于理解,从而提高程序的可读性和开发效率。

以下是一些常见的语法糖例子:

  1. 泛型:允许在定义类型时指定参数化类型,简化代码和提高代码的可读性。3
  1. 变长参数:允许函数定义时接收可变数量的参数,提高代码的灵活性。
  2. 条件编译:允许根据条件编译或排除某些代码段,优化代码性能。
  3. 自动拆装箱:自动将对象转换为基本类型,或将基本类型转换为对象,简化对象操作。
  4. 内部类:允许在类内部定义另一个类,提高代码的封装性和复用性。

使用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 是一个对象,它有三个属性:nameage 和 greetname 和 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属性指向一个包含nameagejob属性的对象。当我们创建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'

在这个例子中,person1person2都是Person的实例,但是它们的原型链指向的是Person.prototype的不同实例。因此,person1person2name属性是从不同的原型链中查找的,得到了不同的值。

  • js分为函数对象和普通对象,每个对象都有__proto__属性,但是只有函数对象才有prototype属性
  • Object、Function都是js内置的函数, 类似的还有我们常用到的Array、RegExp、Date、Boolean、Number、String

继承

原型链继承 

转载JavaScript常用八种继承方案 - 掘金

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中并不使用“静态”这个词来描述它们。

然而,关于callapply,这两个方法实际上是Function对象的方法,而不是Object的方法。它们用于调用一个函数,并允许你指定该函数运行时的this值,以及传递给该函数的参数。这两个方法在函数式编程和回调函数中特别有用,因为它们允许你更灵活地控制函数的执行上下文。

  1. 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.

  1. 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

在这两个例子中,callapply都允许我们指定函数greetsum执行时的this值,并传递参数。它们的主要区别在于如何传递参数:call接受一系列单独的参数,而apply接受一个数组或类似数组的对象作为参数列表。

在你的任务中,如果要探讨Object的一些常用方法以及callapply的使用,你可以深入了解这些方法的用途和如何在实际编程中使用它们。同时,注意区分哪些方法是直接定义在Object上的,哪些方法是定义在Function.prototype上的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值