0%

Background

今天在练习正则表达式的时候,发现了一个有趣的输出,之前未见过(也许曾经见过,但没有留意),索性研究一下。

代码如下:

1
2
3
4
const str = `name: zdd, age: 18, gender: male`;
const regex = /name: (?<name>\w+), age: (?<age>\d+), gender: (?<gender>\w+)/;
const match = str.match(regex);
console.log(match.groups);

简单解释一下这个正则表达式:

  1. /.../ - 这是js的正则表达式语法,用//包裹。
  2. name: - 匹配字符串name:
  3. (?<name>...) - 这是一个命名捕获,表示匹配后的值放到name这变量中,可以用match.groups.name来输出捕获的值。
  4. \w+ - 表示匹配任意字母、数字或下划线(等价于 [a-zA-Z0-9_]), + - 表示一个或者多个。

输出结果如下:

1
[Object: null prototype] { name: 'zdd', age: '18', gender: 'male' }

这个输出结果里面的[Object: null prototype]是什么意思呢?问了一下通义千问,发现这是一个没有原型的对象。

那么,啥是没有原型的对象呢?可以简单的理解为,这个对象没有继承任何Object的属性和方法,是一个干净的对象。它就是它自己!
此类对象没有toStringvalueOf等方法,也没有__proto__属性,因此无法通过原型链访问到Object的属性和方法。我们可以通过Object.create(null)来创建一个没有原型的对象。

以下代码会产生一个TypeError,因为person对象没有原型,所以无法访问toString, hasOwnProperty等方法。

1
2
3
4
5
6
7
const person = Object.create(null);
person.name = 'Philip';
person.age = 18;

console.log(person.toString()); // TypeError: person.toString is not a function
console.log(person.hasOwnProperty('name')); // TypeError: person.hasOwnProperty is not a function
console.log('name' in person); // true

总结

没有原型的对象有哪些好处呢?我们常说,存在即有理,既然这种对象,那么必然有它存在的道理:

优点

  1. 避免原型污染:在JavaScript中,我们经常会遇到原型污染的问题,通过创建没有原型的对象,可以避免这种问题。
  2. 速度快:没有原型的对象,不需要查找原型链,因此访问属性和方法的速度更快。
  3. 纯粹的数据容器:没有原型的对象,可以作为一个纯粹的数据容器。

缺点

  1. 无法使用Object.prototype上的方法,如toStringvalueOf等。
  2. 检查属性是否存在只能使用in操作符,不能使用hasOwnProperty方法。

TypeError: Cannot read properties of undefined (reading ‘xxx’)

This error occurs when you try to access a property of an object that is undefined.

1
2
let person = undefined;
console.log(person.age); // Uncaught TypeError: Cannot read properties of undefined (reading 'age')

ReferenceError: a is not defined

This is the most common error in JavaScript. It means that you are trying to use a variable that has not been declared.

1
console.log(a); // Uncaught ReferenceError: a is not defined

ReferenceError: Cannot access ‘a’ before initialization

This error occurs when you try to access a variable before it is declared. After ES6, this means you touch the (TDZ)temporal dead zone. temporal dead zone is the zone between the start of the block scope and the actual declaration of the variable.

1
2
console.log(a); // Uncaught ReferenceError: Cannot access 'a' before initialization
const a = 1;

TypeError: Assignment to constant variable.

This error occurs when you try to reassign a value to a constant variable.

1
2
const a = 1;
a = 2; // Uncaught TypeError: Assignment to constant variable.

RangeError: Maximum call stack size exceeded

See the following code, why it cause the RangeError: Maximum call stack size exceeded error?

1
2
3
4
5
6
7
8
9
10
11
class Person {
get name() {
return this.name;
}
set name(value) {
this.name = value;
}
}
const person = new Person();
person.name = 'Philip';
console.log(person.name); // undefined

The error is caused by the recursive call to the setter of name.

  1. person.name = 'Philip'; will call the setter set name(value).
  2. Inside the setter, this.name = value; will call the setter again. that’s an infinite loop.

How to fix it?
We can just use another name for the property, like _name.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
get name() {
return this._name;
}

set name(value) {
this._name = value;
}
}

const person = new Person('zdd');
person.name = 'Philip';
console.log(person.name); // undefined

In this way,

  1. person.name = 'Philip'; will call the setter set name(value)
  2. Inside the setter function, we set the value to _name property, not the name property. So it will not cause the infinite loop.

You can find the details of getter and setter from here.

Introduction

This指向问题是JavaScript面试中必考的问题,今天我们就来将this有关的面试题一网打尽!解答此类面试题的关键是 函数是以何种方式调用的,拿到题目后,先确定函数的调用方式,再根据调用方式来判断this的指向。

根据调用方式的不同,this的指向有以下几种情况:

  1. 作为普通函数调用, this指向全局对象。
  2. 作为对象方法调用, this指向调用方法的对象。
  3. 作为构造函数调用, this指向新创建的对象。
  4. 使用apply、call、bind调用, this指向指定的对象。
  5. 箭头函数中的this, this指向其父级作用域的this。
  6. setTimeout函数中的this。
  7. DOM环境中的this。

Conclusion

This的指向只和函数的调用方式相关,与函数的定义位置无关。在面试中,如果遇到this指向的问题,先确定函数的调用方式,再根据调用方式来判断this的指向。

Introduction

What is a closure in JavaScript? This is really a hard question, and I see lots of different answers on the internet. I list some of them below:

  1. A closure is a function that has access to the outer function’s variables.
  2. A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives a function access to its outer scope. In JavaScript, closures are created every time a function is created, at function creation time. - from MDN

Lexical scoping

在JavaScript中,词法作用域(Lexical Scoping)是一种确定变量作用域的规则。词法作用域基于变量在代码中声明的位置来决定其可见性和生命周期。具体来说,词法作用域是在编写代码时就确定下来的,它取决于函数和块(如if语句或循环)的嵌套结构,而不是在运行时动态确定的。

词法作用域的特点
静态确定:词法作用域在代码编写阶段就已经确定。编译器在解析代码时会创建一个作用域链,这个链决定了在何处查找变量。
基于嵌套:作用域是根据函数和块的嵌套关系来定义的。内层作用域可以访问外层作用域中的变量,但外层作用域不能访问内层作用域中的变量。
函数优先:在JavaScript中,函数创建了自己的作用域,即使这个函数没有立即执行,它的作用域也是在定义时就确定的。
词法作用域的例子
下面是一个简单的例子来说明词法作用域:

1
2
3
4
5
6
7
8
9
10
11
12
13
function outerFunction(outerArg) {
var outerVariable = 'I am from the outer function';

function innerFunction(innerArg) {
console.log(outerArg); // 可以访问 outerFunction 的参数
console.log(outerVariable); // 可以访问 outerFunction 的变量
console.log(innerArg); // 可以访问 innerFunction 的参数
}

innerFunction('Hello from innerFunction');
}

outerFunction('Hello from outerFunction');

词法作用域是闭包的基础,因为闭包是在词法作用域的基础上实现的。

闭包的特性

  1. 数据封装性 - 可以用来封装私有变量,模拟模块化。
  2. 保持状态 - 保持函数的状态,比如计数器。防抖和节流使用的都是这个特性。

Use case

Data encapsulation(private variables)

In the follow example, the inner function increase has access to the outer function’s variable count. The variable count is private and cannot be accessed from outside the function createCounter. But you can still change count by invoking the increase function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createCounter() {
let count = 0;

function increase() {
count++;
return count;
}

return {
increase,
};
}

const counter = createCounter();
console.log(counter.increase()); // 1

Note that

1
2
3
return {
increase,
};

is shorthand for

1
2
3
return {
increase: increase,
};

We can also return the function increase directly.(This is not work when you have multiple functions to return, you must use the object literal in that case.)

1
2
3
4
5
6
7
8
function createCounter() {
let count = 0;

return function () {
count++;
return count;
}
}

And you should also change the way to invoke the function since you return an anonymous function. so there is no increase property in the counter object.

1
2
const counter = createCounter();
console.log(counter()); // 1

Simulate module(Modernization)

In the following example, we can simulate a module by using closure. The private variables and methods are defined inside the outer function MyModule. The public variables and methods are returned as an object literal. So the private variables and methods are encapsulated and cannot be accessed from outside the function. The public variables and methods can be accessed by invoking the returned object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function MyModule() {
const privateVariable = 'private variable';
const publicVariable = 'public variable';

function privateMethod() {
console.log(privateVariable);
}

function publicMethod() {
console.log(publicVariable);
}

// If you want to expose the variables/methods, return them.
return {
publicVariable,
publicMethod,
};
}

const myModule = MyModule();
console.log(myModule.publicVariable); // public variable
myModule.publicMethod(); // public method

Event handler(or callback)

Some event handler or callback functions use closure to access the outer function’s variables.
In the following example, the event handler for click event is an anymous function. The function has access to the outer function’s variable count.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<button>Click Me</button>
<p>Click count: <span>0</span></p>
<script>
let count = 0;
const button = document.querySelector("button");
const span = document.querySelector("span");
button.addEventListener("click", function () {
count++;
span.textContent = count;
});
</script>
</body>
</html>

Design patter - Singleton

In the following code, we use a local variable instance to check whether the instance has been created or not. The instance variable must memorize its state during different calls to the getInstance function. So we use closure to achieve this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const Singleton = (function () {
let instance = null;

function createInstance(name) {
return { name: name };
}

function getInstance(name) {
if (!instance) {
instance = createInstance(name);
}
return instance;
}

return {
getInstance,
};
})();

const instance1 = Singleton.getInstance('Philip');
const instance2 = Singleton.getInstance('zdd');
console.log(instance1); // Philip
console.log(instance2); // Philip

Debounce

防抖和节流都是为了解决在短段时间内大量触发某个函数执行而导致的性能问题。

防抖的原理
事件处理函数在事件触发一段时间后(比如500ms)再执行,如果在此时间内(比如500ms)事件再次触发,则重新计时。

防抖的应用
输入框实时搜索,如果不加防抖处理的话,用户每输入一个字符就会调用一次接口,极大的浪费带宽和后端服务器资源。加入防抖后,用户停止输入一段时间后,才调用接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function debounce(func, wait) {
let timeout;
return function () {
const args = arguments;
const context = this;
clearTimeout(timeout);
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
};
}

document.querySelector("#input").addEventListener(
"input",
debounce(function (e) {
document.querySelector("#output").innerHTML =
"You have input: " + e.target.value;
// You can call and server side api here, and debounce make sure the api is not called very often.
}, 500)
);

注意,这个实现比较简单,没有考虑到immediate参数,即是否立即执行函数。如果要实现immediate参数,可以参考下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function debounce(func, wait, immediate = false) {
let timeout;

return function () {
const args = arguments;
const context = this;
const callNow = immediate && !timeout;

if (callNow) {
func.apply(context, args);
}

clearTimeout(timeout);

timeout = setTimeout(function () {
if (!immediate) {
func.apply(context, args);
}
timeout = null;
}, wait);
};
}

Throttle

节流是控制某个事件在一段时间内只触发一次函数调用,经常用在处理用户频繁触发的事件中,比如resize、scroll、mousemove等。此类事件如果不加控制,每秒会触发上百次,极大浪费资源。

以下代码是一个节流函数的实现,它确保在每300ms响应一次resize事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function throttle(func, wait) {
let timeout;
return function() {
const context = this;
const args = arguments;
if (!timeout) {
timeout = setTimeout(function() {
timeout = null;
func.apply(context, args);
}, wait);
}
};
}
let count = 0;
window.addEventListener('resize', throttle(function() {
count++;
document.querySelector('#counter').innerHTML = 'resize triggered: ' + count;
}, 300));

注意,节流函数的另一个实现方式是不是用setTimeout函数,如下:这个版本的好处是,当事件第一次发生时,立即执行处理函数,而上面使用setTimeout函数的版本则要等待wait时间后才执行处理函数。

1
2
3
4
5
6
7
8
9
10
11
function throttle(func, wait) {
let lastTime = 0;
return function (...args) {
const now = new Date().getTime();
const context = this;
if (now - lastTime >= wait) {
func.apply(context, args);
lastTime = now;
}
};
}

Currying

在JavaScript中,柯里化(Currying)是一种将使用多个参数的函数转换成一系列使用单一参数的函数的技术。通过柯里化,我们可以将一个多参数的函数逐步转化为一系列单参数的函数。每个函数都返回一个新的函数,直到所有参数都被提供,最终返回结果。

1
add(a, b) ===> curriedAdd(a)(b)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
}
return function (...args2) {
return curried.apply(this, [...args, ...args2]);
};
};
}

function sum(a, b, c) {
return a + b + c;
}

const currySum = curry(sum);
console.log(currySum(1)(2)(3)); // 6
console.log(currySum(1, 2)(3)); // 6
console.log(currySum(1)(2, 3)); // 6
console.log(currySum(1, 2, 3)); // 6

实际应用
参数复用:通过柯里化,可以预先设置好某些参数,然后得到一个预设了部分参数的新函数。这对于创建特定配置的函数非常有用。

1
2
const add5 = curriedAdd(5);
console.log(add5(2)(3)); // 输出 10
  • 延迟执行:柯里化允许你在需要的时候才完成函数的执行,这在处理异步操作或事件处理时特别有用。
  • 简化高阶函数:在处理高阶函数时,柯里化可以帮助简化代码,使得函数更易于理解和测试。
  • 函数组合:柯里化可以与其他函数式编程概念如函数组合结合使用,以构建复杂的逻辑链路,同时保持代码清晰简洁。
  • 部分应用:柯里化有时也被称作部分应用的一种形式,尽管严格来说二者有所不同。部分应用是指固定一个或多个参数的值,而柯里化则是逐步接收参数的过程。
    柯里化是一种强大的技术,尤其是在函数式编程范式中,它能够帮助开发者写出更加模块化和可重用的代码。

References

  1. https://en.wikipedia.org/wiki/Closure_(computer_programming)
  2. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

Introduction

Reflow and repaint are two important concepts in the browser rendering process. Understanding them can help us optimize the performance of our web pages.

Reflow

Reflow is the process of recalculating the position and size of all elements in the DOM tree. When the layout of the page changes, the browser needs to reflow the page to recalculate the position and size of all elements. The following changes can trigger a reflow:

  • Resizing the window
  • Changing the width or height of an element
  • Changing the padding, margin, or border of an element
  • Changing the font size of an element
  • Changing the position of an element

Repaint

Repaint is the process of updating the pixels on the screen. When the layout of the page changes, the browser needs to repaint the affected elements to update the pixels on the screen. The following changes can trigger a repaint:

  • Changing the background color of an element
  • Changing the text color of an element
  • Changing the visibility of an element
  • Changing the opacity of an element
  • Changing the z-index of an element

How Browser render pages

The browser rendering process consists of several steps:

  1. Parse HTML: The browser parses the HTML code and creates a DOM tree.
  2. Parse CSS: The browser parses the CSS code and creates a CSSOM tree.
  3. Combine DOM and CSSOM: The browser combines the DOM tree and CSSOM tree to create a render tree.
  4. Layout: The browser calculates the position and size of all elements in the render tree.
  5. Paint: The browser paints the pixels on the screen based on the render tree.
  6. Composite: The browser combines the painted pixels to create the final image on the screen.
  7. In the process of rendering a web page, the browser may need to trigger reflows and repaints to update the layout and appearance of the page.
    1. Reflow: Recalculating the position and size of all elements in the DOM tree.
    2. Repaint: Updating the pixels on the screen.
    3. Reflow will trigger repaint, but repaint does not necessarily trigger reflow.

References

  1. https://www.explainthis.io/en/swe/repaint-and-reflow
  2. https://dev.to/gopal1996/understanding-reflow-and-repaint-in-the-browser-1jbg
  3. https://medium.com/sessionstack-blog/how-javascript-works-the-rendering-engine-and-tips-to-optimize-its-performance-7b95553baeda

1. Url changed but page not loaded

Reason: The <router-outlet> is not in the html template.
Solution: Add the <router-outlet> to the template file.

1
2
3
4
5
6
7
8
9
<!-- app.component.html -->
<nav>
<ul>
<li><a routerLink="/home">Home</a></li>
<li><a routerLink="/product">Product</a></li>
<li><a routerLink="/about">About</a></li>
</ul>
</nav>
<router-outlet></router-outlet> <!-- Add this line -->

2. ERROR RuntimeError: NG04002: Cannot match any routes. URL Segment: ‘login’

Reason: Route ‘login’ is not defined in the router configuration.
Solution: Add the route to the router configuration.

1
2
3
4
// app-routes.ts
const routes: Routes = [
{ path: 'login', component: LoginComponent },
]

Reason 2: You use canMatch guard in route configuration and the guard return false.
Solution: Make sure the guard returns true.

1
2
3
4
5
{
path: 'product',
loadComponent: () => import('./product/product.component').then(m => m.ProductComponent),
canMatch: [CanMatchGuard], // <-- CanMatchGuard return false cause this error.
},

3. NG8001: ‘router-outlet’ is not a known element

Reason: The RouterModule is not imported in the module.
Solution: Import the RouterModule in the module.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Module based component(app.module.ts)
import { RouterModule } from '@angular/router';
@NgModule({
imports: [RouterModule] // <-- Add this line
})

// Standalone component
import { RouterModule } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet], // <-- Add this line
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})

What is Angular Router Guard?

Router Guard is a feature in Angular Router that allows you to run some code before the route is activated. It can be used to protect routes from unauthorized access, redirect users to a login page, or perform any other action before the route is activated.

canActivate

Use case: Only allow authenticated users to access the /product page, otherwise redirect them to the login page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// app.routes.ts
export const routes: Routes = [
{
path: 'home',
component: HomeComponent,
},
{
path: 'product',
component: ProductComponent,
canActivate: [AuthGuard],
},
{
path: 'login',
component: LoginComponent,
}
];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// auth-guard.service.ts
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {
}

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): MaybeAsync<GuardResult> {
if (this.authService.isAuthenticated()) {
return true;
} else {
this.router.navigate(['login']);
return false;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
@Injectable({
providedIn: 'root'
})
export class AuthService {
constructor() { }

isAuthenticated() {
// Put authentication logic here
return false;
}
}

canActivateChild

Similar to canActivate, but it is used to protect child routes.
In the following example, only authenticated users can access the child routes of the /product page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// app.routes.ts
export const routes: Routes = [
{
path: 'home',
component: HomeComponent,
},
{
path: 'product',
component: ProductComponent,
children: [
{ path: 'detail', component: ProductDetailComponent },
{ path: 'edit', component: ProductEditComponent },
],
canActivateChild: [AuthGuard],
},
{
path: 'login',
component: LoginComponent,
}
];

canDeactivate

Use case: Confirm with the user before leaving the page. For example, make sure the user wants to leave the page without saving changes.

1
2
3
4
5
6
7
8
9
10
11
12
// app.routes.ts
export const routes: Routes = [
{
path: 'home',
component: HomeComponent,
},
{
path: 'post',
component: PostComponent,
canDeactivate: [SaveGuard],
},
];
1
2
3
4
5
6
7
8
9
10
11
12
13
// save-guard.service.ts
export class SaveGuard implements CanDeactivate<any> {
constructor(private saveService: SaveService) {
}

canDeactivate(component: any, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState: RouterStateSnapshot): MaybeAsync<GuardResult> {
if (this.saveService.hasSavedData()) {
return true;
} else {
return confirm('You have unsaved changes. Do you really want to leave?');
}
}
}
1
2
3
4
5
6
7
8
9
// save.service.ts
export class SaveService {
constructor() { }

hasSavedData() {
// Put logic to check if data is saved
return false;
}
}

canLoad

Use case: Guard when calling loadChildren in the route configuration. Note that canLoad not works for loadComponent.

1
2
3
4
5
6
7
const routes: Routes = [
{
path: 'premium',
canLoad: [PremiumFeatureGuard], // Apply PremiumFeatureGuard to prevent lazy loading
loadChildren: () => import('./premium-feature.module').then(m => m.PremiumFeatureModule)
},
];

Note that canLoad is deprecated, use canMatch instead.

canMatch

Use case: Guard when matching the route. If the guard returns false, the route will not be matched.

1
2
3
4
5
6
// app.routes.ts
{
path: 'product',
loadComponent: () => import('./product/product.component').then(m => m.ProductComponent),
canMatch: [CanMatchGuard],
},
1
2
3
4
5
6
7
8
9
// can-match.guard.ts
export class CanMatchGuard implements CanMatch {
constructor() {
}

canMatch(route: Route, segments: UrlSegment[]): MaybeAsync<GuardResult> {
return false;
}
}

This post will show you how to style the active link in Angular Router with routerLinkActive and routerLinkActiveOptions.

routerLinkActive

routerLinkActive is a directive that adds a CSS class to the element when the link’s route becomes active.

We set a class name active to the active link, and apply a red background color to the active link. When user click the home or about link, the background color of the active link will change to red.

1
2
3
4
5
6
<ul>
<li><a routerLink="/home" routerLinkActive="active">Home</a></li>
<li><a routerLink="/about" routerLinkActive="active">About</a></li>
</ul>

<router-outlet/>
1
2
3
.active {
background-color: red;
}

routerLinkActiveOptions

routerLinkActiveOptions is an input property of routerLinkActive directive that allows you to set the options for the active link. It provides a finer-grained control over the behavior of the active link.

Let’s take a look at the definition of routerLinkActiveOptions:

1
2
3
4
5
6
7
8
9
10
routerLinkActiveOptions: {
exact: boolean;
} | IsActiveMatchOptions;

export declare interface IsActiveMatchOptions {
matrixParams: 'exact' | 'subset' | 'ignored';
queryParams: 'exact' | 'subset' | 'ignored';
paths: 'exact' | 'subset';
fragment: 'exact' | 'ignored';
}

Scenario 1: exact: true

When exact: true, the active link will only be set when the URL is exactly the same as the link’s URL.

1
2
3
4
<ul>
<li><a routerLink="/home" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Home</a></li>
<li><a routerLink="/about" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">About</a></li>
</ul>

With this settings, when user input /home/123 in the browser, the home link will not be active, since /home is not exactly the same as /home/123.

Scenario 2: match fragment

When fragment: 'exact', the active link will only be set when the URL’s fragment is exactly the same as the link’s fragment.

1
2
3
4
5
6
const isActiveMatchOptions: IsActiveMatchOptions = {
matrixParams: 'exact',
queryParams: 'exact',
paths: 'exact',
fragment: 'exact'
};
1
2
3
4
<ul>
<li><a routerLink="/home" routerLinkActive="active" [routerLinkActiveOptions]="isActiveMatchOptions">Home</a></li>
<li><a routerLink="/about" routerLinkActive="active" [routerLinkActiveOptions]="isActiveMatchOptions">About</a></li>
</ul>

With this settings, when user input /home#section1 in the browser, the home link will not be active, since the fragment is not exactly the same as the link’s fragment.

You can also config the matrixParams, queryParams, and paths in the IsActiveMatchOptions object to control the active link behavior.

Introduction

Today I got the following error after upgrade to Android Studio LadyBug.

1
Your build is currently configured to use Java 21.0.3 and Gradle 8.0.

So I reinstall the old version android studio Koala and the error still exists. So I google it and found the following solution on stackoverflow.

  1. Open File | Settings | Build, Execution, Deployment | Build Tools | Gradle
  2. Downgrade the JDK version to previous version, for example jbr-17.

android-grade-settings

The reason that old version still not work is because after upgrade Android Studio to LadyBug, it automatically set Java 21.0.3 and Gradle 8.0. Even if you downgrade the Android Studio, the settings still remain the same. So you need to manually change the settings to make it work.

Introduction

There are two types of SDK location in Android, global SDK location which is used by all projects and project SDK location which is used by a specific project.

Global SDK Location

In Android Studio, open File | Settings | Languages & Frameworks | Android SDK. Here you can see the global SDK location.
Android SDK Location

Project SDK Location

  1. Open Android Studio, go to File -> Project Structure -> SDK Location. Here you can see the project SDK location.
    Android SDK Location
  2. If your project has file local.properties, you can specify the project SDK location by adding sdk.dir=/path/to/android/sdk in the file. For example:
1
sdk.dir=D\:\\cache\\android_sdk