# JS questions

function bark() {
  console.log("Woof!");
}

bark.animal = "dog";
//Nothing, this is totally fine!
//这在JavaScript中是可能的,因为函数也是对象!(原始类型之外的所有东西都是对象)
function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

const sarah = Person("Sarah", "Smith");

console.log(sarah);
//ans :undefined
// 不添加new它指的是全局对象,sarah本身的返回值是undefined
  1. 事件传播的三个阶段

捕获 > 目标 > 冒泡,在捕获阶段,事件通过父元素向下传递到目标元素。 然后它到达目标元素,冒泡开始

  1. 除基础对象外,所有对象都有原型,基础对象指原型链终点的对象。基础对象的原型是null

function getPersonInfo(one, two, three) {
  console.log(one);
  console.log(two);
  console.log(three);
}

const person = "Lydia";
const age = 21;

getPersonInfo`${person} is ${age} years old`;
// ["", "is", "years old"] Lydia 21
//第一个参数的值始终是字符串值的数组。 其余参数获取传递到模板字符串中的表达式的值!
function checkAge(data) {
  if (data === { age: 18 }) {
    console.log("You are an adult!");
  } else if (data == { age: 18 }) {
    console.log("You are still an adult.");
  } else {
    console.log(`Hmm.. You don't have an age I guess`);
  }
}

checkAge({ age: 18 });
//Hmm.. You don't have an age I guess
//比较相等性,原始类型通过它们的值进行比较,而对象通过它们的引用进行比较。
//JavaScript检查对象是否具有对内存中相同位置的引用。
const obj = { 1: "a", 2: "b", 3: "c" };
const set = new Set([1, 2, 3, 4, 5]);

obj.hasOwnProperty("1");
obj.hasOwnProperty(1);
set.has("1");
set.has(1);
//true true false true
// 所有对象键(不包括Symbols)都会被存储为字符串,即使你没有给定字符串类型的键
const obj = { a: "one", b: "two", a: "three" };
console.log(obj);
//{ a: "three", b: "two" }
//对象有两个具有相同名称的键,则将替前面的键。它仍将处于第一个位置,但具有最后指定的值。
const a = {};
const b = { key: "b" };
const c = { key: "c" };

a[b] = 123;
a[c] = 456;

console.log(a[b]);
//456
//当对象自动转换为字符串化时,它变成了[Object object]
//我们打印a[b],它实际上是a["Object object"]
<div onclick="console.log('first div')">
  <div onclick="console.log('second div')">
    <button onclick="console.log('button')">
      Click!
    </button>
  </div>
</div>
//点击button event.target==> button
// 导致事件的最深嵌套元素是事件的目标
<div onclick="console.log('div')">
  <p onclick="console.log('p')">
    Click here!
  </p>
</div>
//p  div
//在事件传播期间,有三个阶段:捕获,目标和冒泡。 默认情况下,
// 事件处理程序在冒泡阶段执行(除非您将useCapture设置为true)。 它从最深的嵌套元素向外延伸。
const person = { name: "Lydia" };

function sayHi(age) {
  console.log(`${this.name} is ${age}`);
}

sayHi.call(person, 21);
sayHi.bind(person, 21);
//Lydia is 21   function
//call方法会立即执行!
//.bind方法会返回函数的拷贝值,但带有绑定的上下文! 它不会立即执行
0;
new Number(0);
("");
(" ");
new Boolean(false);
undefined;
//假值 0,'',undefined

//JavaScript中只有6个假值:
undefined
null
NaN
0
'' (empty string)
false
const numbers = [1, 2, 3];
numbers[10] = 11;
console.log(numbers);
//[1, 2, 3, 7 x empty, 11]
//你为数组中的元素设置一个超过数组长度的值时,JavaScript会创建一个名为“空插槽”的东西。 
//这些位置的值实际上是undefined
(() => {
  let x, y;
  try {
    throw new Error();
  } catch (x) {
    (x = 1), (y = 2);
    console.log(x);
  }
  console.log(x);
  console.log(y);
})();
//1 undefined 2
//catch块接收参数x。当我们传递参数时,这与变量的x不同。这个变量x是属于catch作用域的。
//在catch块之外,x仍然是undefined,而y是2
!!null;
!!"";
!!1;
// false false true
let person = { name: "Lydia" };
const members = [person];
person = null;
console.log(members);
//[{ name: "Lydia" }]
//只是修改了变量person的值,因为元素(复制而来)的引用与person不同。
//members的第一个元素仍然保持着对原始对象的引用
const num = parseInt("7*6", 10);
//7
//*是不合法的数字字符。所以只解析到"7",并将其解析为十进制的7.
[1, 2, 3].map(num => {
  if (typeof num === "number") return;
  return num * 2;
});
//[undefined, undefined, undefined]
//没有任何值返回。当函数没有返回任何值时,即默认返回undefined
function getInfo(member, year) {
  member.name = "Lydia";
  year = "1998";
}
const person = { name: "Sarah" };
const birthYear = "1997";
getInfo(person, birthYear);
console.log(person, birthYear);
//{ name: "Lydia" }, "1997"
//普通参数都是 值 传递的,而对象则不同,是 引用 传递
function greeting() {
  throw "Hello world!";
}
function sayHi() {
  try {
    const data = greeting();
    console.log("It worked!", data);
  } catch (e) {
    console.log("Oh no an error!", e);
  }
}
sayHi();
//"Oh no an error: Hello world!
function Car() {
  this.make = "Lamborghini";
  return { make: "Maserati" };
}
const myCar = new Car();
console.log(myCar.make);
//Maserati
//返回属性的时候,属性的值等于 返回的 值,而不是构造函数中设定的值
(() => {
  let x = (y = 10);
})();

console.log(typeof x);
console.log(typeof y);
//undefined number
//let x = y = 10 ==>
//y=10;let x=y y是全局变量
// counter.js
let counter = 10;
export default counter;
// index.js
import myCounter from "./counter";
myCounter += 1;
console.log(myCounter);
//Error 引入的模块是 只读 的: 你不能修改引入的模块。只有导出他们的模块才能修改其值。
const name = "Lydia";
age = 21;

console.log(delete name);
console.log(delete age);
//false, true
//通过 var, const 或 let 关键字声明的变量无法用 delete 操作符来删除。
//age 挂到window 对象上
const person = { name: "Lydia" };
Object.defineProperty(person, "age", { value: 21 });
console.log(person);
console.log(Object.keys(person));
//{ name: "Lydia", age: 21 }, ["name"]
//属性默认为 不可枚举(not enumerable). Object.keys方法仅返回对象中 可枚举(enumerable) 的属性,因此只剩下了"name".
//用defineProperty方法添加的属性默认不可变。你可以通过writable, configurable 和 enumerable属性来改变这一行为
const settings = {
  username: "lydiahallie",
  level: 19,
  health: 90
};
const data = JSON.stringify(settings, ["level", "health"]);
console.log(data);
//"{"level":19, "health":90}"
//JSON.stringify的第二个参数是 替代者(replacer). 替代者(replacer)可以是个函数或数组,用以控制哪些值如何被转换为字符串。
//如果替代者(replacer)是个 数组 ,那么就只有包含在数组中的属性将会被转化为字符串。在本例中,只有名为"level" 和 "health" 的属性被包括进来, "username"则被排除在外。 data 就等于 "{"level":19, "health":90}".
//而如果替代者(replacer)是个 函数,这个函数将被对象的每个属性都调用一遍。 函数返回的值会成为这个属性的值,最终体现在转化后的JSON字符串中(译者注:Chrome下,经过实验,如果所有属性均返回同一个值的时候有异常,会直接将返回值作为结果输出而不会输出JSON字符串),而如果返回值为undefined,则该属性会被排除在外。
let num = 10;
const increaseNumber = () => num++;
const increasePassedNumber = number => number++;
const num1 = increaseNumber();
const num2 = increasePassedNumber(num1);

console.log(num1);
console.log(num2);
//10,10
const value = { number: 10 };
const multiply = (x = { ...value }) => {
  console.log(x.number *= 2);
};
multiply();
multiply();
multiply(value);
multiply(value);
//20 20 20 40   *=运算符实际上是x.number = x.number * 2的简写
[1, 2, 3, 4].reduce((x, y) => console.log(x, y));
//1 2 and undefined 3 and undefined 4
//我们的回调函数没有返回任何值,只是打印累加器的值和当前值。如果函数没有返回值,则默认返回undefined
class Dog {
  constructor(name) {
    this.name = name;
  }
};
class Labrador extends Dog {
  // 1 
  constructor(name, size) {
    this.size = size;
  }
  // 2
  constructor(name, size) {
    super(name);
    this.size = size;
  }
  // 3
  constructor(size) {
    super(name);
    this.size = size;
  }
  // 4 
  constructor(name, size) {
    this.name = name;
    this.size = size;
  }
};
//2能成功继承
//子类中,在调用super之前不能访问到this关键字。 如果这样做,它将抛出一个ReferenceError
//使用super关键字,需要用给定的参数来调用父类的构造函数。 父类的构造函数接收name参数,因此我们需要将name传递给super
// index.js
console.log('running index.js');
import { sum } from './sum.js';
console.log(sum(1, 2));

// sum.js
console.log('running sum.js');
export const sum = (a, b) => a + b;
//running sum.js, running index.js, 3
// import命令是编译阶段执行的,在代码运行之前。因此这意味着被导入的模块会先运行,而导入模块的文件会后执行
function* startGame() {
  const answer = yield "Do you love JavaScript?";
  if (answer !== "Yes") {
    return "Oh wow... Guess we're gone here";
  }
  return "JavaScript loves you back ❤️";
}
const game = startGame();
console.log(/* 1 */); // Do you love JavaScript?
console.log(/* 2 */); // JavaScript loves you back ❤️
//game.next().value and game.next("Yes").value
//next方法可以带一个参数,该参数会被当作上一个 yield 表达式的返回值
console.log(String.raw`Hello\nworld`);
//Hello\nworld
//String.raw函数是用来获取一个模板字符串的原始字符串的,它返回一个字符串,其中忽略了转义符(\n,\v,\t等)
const add = () => {
  const cache = {};
  return num => {
    if (num in cache) {
      return `From cache! ${cache[num]}`;
    } else {
      const result = num + 10;
      cache[num] = result;
      return `Calculated! ${result}`;
    }
  };
};
const addFunction = add();
console.log(addFunction(10));
console.log(addFunction(10));
console.log(addFunction(5 * 2));
//Calculated! 20 From cache! 20 From cache! 20
//add函数是一个记忆函数。 通过记忆化,我们可以缓存函数的结果,以加快其执行速度
function sayHi(name) {
  return `Hi there, ${name}`
}
console.log(sayHi())
//Hi there, undefined
//默认情况下,如果不给函数传参,参数的值将为undefined
const person = {
  name: "Lydia",
  age: 21
}
let city = person.city
city = "Amsterdam"
console.log(person)
//{ name: "Lydia", age: 21 }
//没有引用person对象本身,只是将变量city设置为等于person对象上city属性的当前值
function getItems(fruitList, ...args, favoriteFruit) {
  return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange")
//SyntaxError
//... args是剩余参数,剩余参数的值是一个包含所有剩余参数的数组,并且只能作为最后一个参数
function nums(a, b) {
  if
  (a > b)
  console.log('a is bigger')
  else 
  console.log('b is bigger')
  return 
  a + b
}
console.log(nums(4, 2))
console.log(nums(1, 2))
//a is bigger, undefined and b is bigger, undefined
//在JavaScript中,我们不必显式地编写分号(;),但是JavaScript引擎仍然在语句之后自动添加分号。这称为自动分号插入。
//看做 return;
// a + b
class Person {
  constructor() {
    this.name = "Lydia"
  }
}
Person = class AnotherPerson {
  constructor() {
    this.name = "Sarah"
  }
}
const member = new Person()
console.log(member.name)
//Sarah
//可以将类设置为等于其他类/函数构造函数。
const info = {
  [Symbol('a')]: 'b'
}
console.log(info)
console.log(Object.keys(info))
// {Symbol('a'): 'b'} and []
// Symbol类型是不可枚举的。Object.keys方法返回对象上的所有可枚举的键属性。Symbol类型是不可见的,并返回一个空数组
//除了表示完全唯一的值(防止对象意外名称冲突,例如当使用2个想要向同一对象添加属性的库时),
//您还可以隐藏这种方式对象的属性(尽管不完全。你仍然可以使用Object.getOwnPropertySymbols()方法访问 Symbol。
const getUser = user => { name: user.name, age: user.age }
const user = { name: "Lydia", age: 21 }
console.log(getUser(user))
//undefined
//如果您想从一个箭头函数返回一个对象,您必须在圆括号之间编写它,否则不会返回任何值
const name = "Lydia"
console.log(name())
//TypeError
// 当值不是预期类型时,会抛出TypeErrors。 
//编写了一些非有效的JavaScript时,会抛出语法错误
const one = (false || {} || null)
const two = (null || false || "")
const three = ([] || 0 || true)
console.log(one, two, three)  
//{} "" []
//使用||运算符,我们可以返回第一个真值。 如果所有值都是假值,则返回最后一个值。